Skip to content

Commit cf869b2

Browse files
committed
[__main__] add executable support for table inclusion and exclusion filters
new command line arguments and comma separated table definition syntax
1 parent f3ca580 commit cf869b2

File tree

2 files changed

+190
-7
lines changed

2 files changed

+190
-7
lines changed

lib/fdiff/__main__.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from fdiff import __version__
99
from fdiff.color import color_unified_diff_line
1010
from fdiff.diff import u_diff
11-
from fdiff.utils import file_exists
11+
from fdiff.utils import file_exists, get_tables_argument_list
1212

1313

1414
def main(): # pragma: no cover
@@ -28,9 +28,9 @@ def main(): # pragma: no cover
2828

2929

3030
def run(argv):
31-
# ===========================================================
31+
# ------------------------------------------
3232
# argparse command line argument definitions
33-
# ===========================================================
33+
# ------------------------------------------
3434
parser = argparse.ArgumentParser(
3535
description="An OpenType table diff tool for fonts."
3636
)
@@ -47,14 +47,43 @@ def run(argv):
4747
parser.add_argument(
4848
"-l", "--lines", type=int, default=3, help="Number of context lines (default 3)"
4949
)
50+
parser.add_argument(
51+
"--include",
52+
type=str,
53+
default=None,
54+
help="Comma separated list of tables to include",
55+
)
56+
parser.add_argument(
57+
"--exclude",
58+
type=str,
59+
default=None,
60+
help="Comma separated list of tables to exclude",
61+
)
5062
parser.add_argument("PREFILE", help="Font file path 1")
5163
parser.add_argument("POSTFILE", help="Font file path 2")
5264

5365
args = parser.parse_args(argv)
5466

67+
# /////////////////////////////////////////////////////////
5568
#
56-
# File path argument validations
69+
# Validations
5770
#
71+
# /////////////////////////////////////////////////////////
72+
73+
# ----------------------------------
74+
# Incompatible argument validations
75+
# ----------------------------------
76+
# --include and --exclude are mutually exclusive options
77+
if args.include and args.exclude:
78+
sys.stderr.write(
79+
f"[*] Error: --include and --exclude are mutually exclusive options. "
80+
f"Please use ONLY one of these options in your command.{os.linesep}"
81+
)
82+
sys.exit(1)
83+
84+
# -------------------------------
85+
# File path argument validations
86+
# -------------------------------
5887

5988
if not file_exists(args.PREFILE):
6089
sys.stderr.write(
@@ -67,18 +96,40 @@ def run(argv):
6796
)
6897
sys.exit(1)
6998

99+
# /////////////////////////////////////////////////////////
70100
#
71-
# Unified diff logic
101+
# Command line logic
72102
#
103+
# /////////////////////////////////////////////////////////
73104

105+
# ---------------
106+
# Unified diff
107+
# ---------------
108+
109+
# parse explicitly included or excluded tables in
110+
# the command line arguments
111+
# set as a Python list if it was defined on the command line
112+
# or as None if it was not set on the command line
113+
include_list = get_tables_argument_list(args.include)
114+
exclude_list = get_tables_argument_list(args.exclude)
115+
116+
# perform the unified diff analysis
74117
try:
75-
diff = u_diff(args.PREFILE, args.POSTFILE, context_lines=args.lines)
118+
diff = u_diff(
119+
args.PREFILE,
120+
args.POSTFILE,
121+
context_lines=args.lines,
122+
include_tables=include_list,
123+
exclude_tables=exclude_list,
124+
)
76125
except Exception as e:
77126
sys.stderr.write(
78-
f"[*] ERROR: During the attempt to diff the requested files the following error was encountered: {str(e)}"
127+
f"[*] ERROR: During the attempt to diff the requested files the following error was encountered: "
128+
f"{e}{os.linesep}"
79129
)
80130
sys.exit(1)
81131

132+
# print unified diff results to standard output stream
82133
if args.color:
83134
for line in diff:
84135
sys.stdout.write(color_unified_diff_line(line))

tests/test_main.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
ROBOTO_UDIFF_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_expected.txt")
1313
ROBOTO_UDIFF_COLOR_EXPECTED_PATH = os.path.join("tests", "testfiles", "roboto_udiff_color_expected.txt")
1414
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")
1518

1619

1720
# Setup: define the expected diff text for unified diff
@@ -27,6 +30,18 @@
2730
with open(ROBOTO_UDIFF_1CONTEXT_EXPECTED_PATH, "r") as robo_udiff_contextlines:
2831
ROBOTO_UDIFF_1CONTEXT_EXPECTED = robo_udiff_contextlines.read()
2932

33+
# Setup: define the expected diff text for head table only diff
34+
with open(ROBOTO_UDIFF_HEADONLY_EXPECTED_PATH, "r") as robo_udiff_headonly:
35+
ROBOTO_UDIFF_HEADONLY_EXPECTED = robo_udiff_headonly.read()
36+
37+
# Setup: define the expected diff text for head and post table only diff
38+
with open(ROBOTO_UDIFF_HEADPOSTONLY_EXPECTED_PATH, "r") as robo_udiff_headpostonly:
39+
ROBOTO_UDIFF_HEADPOSTONLY_EXPECTED = robo_udiff_headpostonly.read()
40+
41+
# Setup: define the expected diff text for head and post table only diff
42+
with open(ROBOTO_UDIFF_EXCLUDE_HEADPOST_EXPECTED_PATH, "r") as robo_udiff_ex_headpost:
43+
ROBOTO_UDIFF_EXCLUDE_HEADPOST_EXPECTED = robo_udiff_ex_headpost.read()
44+
3045
#
3146
# File path validations tests
3247
#
@@ -54,6 +69,20 @@ def test_main_filepath_validations_false_secondfont(capsys):
5469
assert exit_info.value.code == 1
5570

5671

72+
#
73+
# Mutually exclusive argument tests
74+
#
75+
76+
def test_main_include_exclude_defined_simultaneously(capsys):
77+
args = ["--include", "head", "--exclude", "head", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
78+
79+
with pytest.raises(SystemExit) as exit_info:
80+
run(args)
81+
captured = capsys.readouterr()
82+
assert captured.error.startswith("[*] Error: --include and --exclude are mutually exclusive options")
83+
assert exit_info.value.code == 1
84+
85+
5786
#
5887
# Unified diff integration tests
5988
#
@@ -119,3 +148,106 @@ def test_main_run_unified_context_lines_1(capsys):
119148
assert line[0:9] == expected_string_list[x][0:9]
120149
else:
121150
assert line == expected_string_list[x]
151+
152+
153+
def test_main_run_unified_head_table_only(capsys):
154+
args = ["--include", "head", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
155+
156+
run(args)
157+
captured = capsys.readouterr()
158+
159+
res_string_list = captured.out.split("\n")
160+
expected_string_list = ROBOTO_UDIFF_HEADONLY_EXPECTED.split("\n")
161+
162+
# have to handle the tests for the top two file path lines
163+
# differently than the rest of the comparisons because
164+
# the time is defined using local platform settings
165+
# which makes tests fail on different remote CI testing services
166+
for x, line in enumerate(res_string_list):
167+
# treat top two lines of the diff as comparison of first 10 chars only
168+
if x in (0, 1):
169+
assert line[0:9] == expected_string_list[x][0:9]
170+
else:
171+
assert line == expected_string_list[x]
172+
173+
174+
def test_main_run_unified_head_post_tables_only(capsys):
175+
args = ["--include", "head,post", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
176+
177+
run(args)
178+
captured = capsys.readouterr()
179+
180+
res_string_list = captured.out.split("\n")
181+
expected_string_list = ROBOTO_UDIFF_HEADPOSTONLY_EXPECTED.split("\n")
182+
183+
# have to handle the tests for the top two file path lines
184+
# differently than the rest of the comparisons because
185+
# the time is defined using local platform settings
186+
# which makes tests fail on different remote CI testing services
187+
for x, line in enumerate(res_string_list):
188+
# treat top two lines of the diff as comparison of first 10 chars only
189+
if x in (0, 1):
190+
assert line[0:9] == expected_string_list[x][0:9]
191+
else:
192+
assert line == expected_string_list[x]
193+
194+
195+
def test_main_run_unified_exclude_head_post_tables(capsys):
196+
args = ["--exclude", "head,post", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
197+
198+
run(args)
199+
captured = capsys.readouterr()
200+
201+
res_string_list = captured.out.split("\n")
202+
expected_string_list = ROBOTO_UDIFF_EXCLUDE_HEADPOST_EXPECTED.split("\n")
203+
204+
# have to handle the tests for the top two file path lines
205+
# differently than the rest of the comparisons because
206+
# the time is defined using local platform settings
207+
# which makes tests fail on different remote CI testing services
208+
for x, line in enumerate(res_string_list):
209+
# treat top two lines of the diff as comparison of first 10 chars only
210+
if x in (0, 1):
211+
assert line[0:9] == expected_string_list[x][0:9]
212+
else:
213+
assert line == expected_string_list[x]
214+
215+
216+
def test_main_include_with_bad_table_definition(capsys):
217+
args = ["--include", "bogus", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
218+
219+
with pytest.raises(SystemExit) as exit_info:
220+
run(args)
221+
captured = capsys.readouterr()
222+
assert captured.error.startswith("[*] ERROR:")
223+
assert exit_info.value.code == 1
224+
225+
226+
def test_main_include_with_bad_table_definition_in_multi_table_request(capsys):
227+
args = ["--include", "head,bogus", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
228+
229+
with pytest.raises(SystemExit) as exit_info:
230+
run(args)
231+
captured = capsys.readouterr()
232+
assert captured.error.startswith("[*] ERROR:")
233+
assert exit_info.value.code == 1
234+
235+
236+
def test_main_exclude_with_bad_table_definition(capsys):
237+
args = ["--exclude", "bogus", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
238+
239+
with pytest.raises(SystemExit) as exit_info:
240+
run(args)
241+
captured = capsys.readouterr()
242+
assert captured.error.startswith("[*] ERROR:")
243+
assert exit_info.value.code == 1
244+
245+
246+
def test_main_exclude_with_bad_table_definition_in_multi_table_request(capsys):
247+
args = ["--exclude", "head,bogus", ROBOTO_BEFORE_PATH, ROBOTO_AFTER_PATH]
248+
249+
with pytest.raises(SystemExit) as exit_info:
250+
run(args)
251+
captured = capsys.readouterr()
252+
assert captured.error.startswith("[*] ERROR:")
253+
assert exit_info.value.code == 1

0 commit comments

Comments
 (0)