Skip to content

Commit 6e56ab6

Browse files
authored
Merge pull request #359 from cs50/patch-invis-chars
Make Truncating Configurable
2 parents adb4a10 + 4f145bd commit 6e56ab6

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

check50/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def _setup_translation():
4747

4848
from . import regex
4949
from .runner import check
50+
from .config import config
5051
from pexpect import EOF
5152

5253
__all__ = ["import_checks", "data", "exists", "hash", "include", "regex",
53-
"run", "log", "Failure", "Mismatch", "Missing", "check", "EOF"]
54+
"run", "log", "Failure", "Mismatch", "Missing", "check", "EOF",
55+
"config"]

check50/_api.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pexpect.exceptions import EOF, TIMEOUT
1212

1313
from . import internal, regex
14+
from .config import config
1415

1516
_log = []
1617
internal.register.before_every(_log.clear)
@@ -499,7 +500,7 @@ def wrapper(*args, **kwargs):
499500
return wrapper
500501
return decorator
501502

502-
def _truncate(s, other, max_len=10):
503+
def _truncate(s, other):
503504
def normalize(obj):
504505
if isinstance(obj, list):
505506
return "\n".join(map(str, obj))
@@ -508,6 +509,11 @@ def normalize(obj):
508509

509510
s, other = normalize(s), normalize(other)
510511

512+
if not config.dynamic_truncate:
513+
if len(s) > config.truncate_len:
514+
s = s[:config.truncate_len] + "..."
515+
return s
516+
511517
# find the index of first difference
512518
limit = min(len(s), len(other))
513519
i = limit
@@ -517,8 +523,8 @@ def normalize(obj):
517523
break
518524

519525
# center around diff
520-
start = max(i - (max_len // 2), 0)
521-
end = min(start + max_len, len(s))
526+
start = max(i - (config.truncate_len // 2), 0)
527+
end = min(start + config.truncate_len, len(s))
522528

523529
snippet = s[start:end]
524530

check50/config.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
class Config:
2+
"""
3+
Configuration for `check50` behavior.
4+
5+
This class stores user-defined configuration options that influence
6+
check50's output formatting.
7+
8+
For developers of `check50`, you can extend the `Config` class by adding new
9+
variables to the `__init__`, which will automatically generate new "setter"
10+
functions to modify the default values. Additionally, if the new
11+
configuration needs to be validated before the user can modify it, add your
12+
validation into the `_validators` dictionary.
13+
"""
14+
15+
def __init__(self):
16+
self.truncate_len = 10
17+
self.dynamic_truncate = True
18+
19+
# Create boolean validators for your variables here (if needed):
20+
# A help message is not required.
21+
self._validators = {
22+
"truncate_len": (lambda val: isinstance(val, int) and val >= 1,
23+
"truncate_len must be a positive integer"),
24+
"dynamic_truncate": (lambda val: isinstance(val, bool) or val in (0, 1),
25+
"dynamic_truncate must be a boolean")
26+
}
27+
28+
# Dynamically generates setter functions based on variable names and
29+
# the type of the default values
30+
self._generate_setters()
31+
32+
def _generate_setters(self):
33+
def make_setter(attr):
34+
"""Factory for making functions like `set_<attr_name>(arg)`"""
35+
36+
def setter(self, value):
37+
# Get the entry in the dict of validators.
38+
# Check to see if the value passes the validator, and if it
39+
# didn't, display the help message, if any.
40+
validator_entry = self._validators.get(attr)
41+
42+
if validator_entry:
43+
if isinstance(validator_entry, tuple):
44+
validator, help = validator_entry
45+
else:
46+
validator, help = validator_entry, None
47+
48+
if not validator(value):
49+
error_msg = f"invalid value for {attr}: {value}"
50+
if help:
51+
error_msg += f", {help}"
52+
raise ValueError(error_msg)
53+
54+
setattr(self, attr, value)
55+
return setter
56+
57+
# Iterate through the names of every instantiated variable
58+
for attribute_name in self.__dict__:
59+
if attribute_name.startswith('_'):
60+
continue # skip "private" attributes (denoted with a prefix `_`)
61+
value = getattr(self, attribute_name)
62+
if callable(value):
63+
continue # skip functions/methods
64+
65+
# Create a class method with the given name and function
66+
setattr(self.__class__, f"set_{attribute_name}", make_setter(attribute_name))
67+
68+
69+
config = Config()

0 commit comments

Comments
 (0)