diff options
| author | Árni Dagur <arni@dagur.eu> | 2021-01-16 19:47:05 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-16 19:47:05 +0000 |
| commit | 9c684f12e1050d8b0dce9cb1e66eb2de0c5f3be6 (patch) | |
| tree | dc7de30b8e0e653d04056aed28673c04656301f9 | |
| parent | 78f97d6b35b613b78727a94f3f6064b091665f55 (diff) | |
Improve __repr__ (#23)
So we get something like:
BlockerResult(matched=false, important=false, redirect=None, exception=None, filter=None, error=None)
rather than:
BlockerResult(false, false, None, None, None, None)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/lib.rs | 77 | ||||
| -rw-r--r-- | tests/test_repr.py | 37 |
3 files changed, 109 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e225fa9..ca861ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch | |||
| 12 | 12 | ||
| 13 | ### Changes | 13 | ### Changes |
| 14 | * Updated PyO3 to version `0.13`. | 14 | * Updated PyO3 to version `0.13`. |
| 15 | * Changed `__repr__` methods of classes to be more idiomatic. | ||
| 15 | 16 | ||
| 16 | ### Fixes | 17 | ### Fixes |
| 17 | 18 | ||
| @@ -99,8 +99,13 @@ impl Into<BlockerResult> for RustBlockerResult { | |||
| 99 | impl PyObjectProtocol for BlockerResult { | 99 | impl PyObjectProtocol for BlockerResult { |
| 100 | fn __repr__(&self) -> PyResult<String> { | 100 | fn __repr__(&self) -> PyResult<String> { |
| 101 | Ok(format!( | 101 | Ok(format!( |
| 102 | "BlockerResult({}, {}, {:?}, {:?}, {:?}, {:?})", | 102 | "BlockerResult(matched={}, important={}, redirect={}, exception={}, filter={}, error={})", |
| 103 | self.matched, self.important, self.redirect, self.exception, self.filter, self.error | 103 | self.matched.diy_python_repr(), |
| 104 | self.important.diy_python_repr(), | ||
| 105 | self.redirect.diy_python_repr(), | ||
| 106 | self.exception.diy_python_repr(), | ||
| 107 | self.filter.diy_python_repr(), | ||
| 108 | self.error.diy_python_repr(), | ||
| 104 | )) | 109 | )) |
| 105 | } | 110 | } |
| 106 | } | 111 | } |
| @@ -173,6 +178,7 @@ fn filter_format_from_string(filter_format: &str) -> PyResult<FilterFormat> { | |||
| 173 | #[derive(Clone)] | 178 | #[derive(Clone)] |
| 174 | pub struct FilterSet { | 179 | pub struct FilterSet { |
| 175 | filter_set: RustFilterSet, | 180 | filter_set: RustFilterSet, |
| 181 | debug: bool, | ||
| 176 | } | 182 | } |
| 177 | 183 | ||
| 178 | #[pymethods] | 184 | #[pymethods] |
| @@ -186,6 +192,7 @@ impl FilterSet { | |||
| 186 | pub fn new(debug: bool) -> Self { | 192 | pub fn new(debug: bool) -> Self { |
| 187 | Self { | 193 | Self { |
| 188 | filter_set: RustFilterSet::new(debug), | 194 | filter_set: RustFilterSet::new(debug), |
| 195 | debug, | ||
| 189 | } | 196 | } |
| 190 | } | 197 | } |
| 191 | 198 | ||
| @@ -215,6 +222,14 @@ impl FilterSet { | |||
| 215 | Ok(()) | 222 | Ok(()) |
| 216 | } | 223 | } |
| 217 | } | 224 | } |
| 225 | |||
| 226 | #[pyproto] | ||
| 227 | impl PyObjectProtocol for FilterSet { | ||
| 228 | fn __repr__(&self) -> PyResult<String> { | ||
| 229 | Ok(format!("FilterSet(debug={})", self.debug.diy_python_repr())) | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 218 | /// Contains cosmetic filter information intended to be injected into a | 233 | /// Contains cosmetic filter information intended to be injected into a |
| 219 | /// particular hostname. | 234 | /// particular hostname. |
| 220 | #[pyclass] | 235 | #[pyclass] |
| @@ -260,12 +275,12 @@ impl Into<UrlSpecificResources> for RustUrlSpecificResources { | |||
| 260 | impl PyObjectProtocol for UrlSpecificResources { | 275 | impl PyObjectProtocol for UrlSpecificResources { |
| 261 | fn __repr__(&self) -> PyResult<String> { | 276 | fn __repr__(&self) -> PyResult<String> { |
| 262 | Ok(format!( | 277 | Ok(format!( |
| 263 | "UrlSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={:?}, generichide={}>", | 278 | "UrlSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={}, generichide={}>", |
| 264 | self.hide_selectors.len(), | 279 | self.hide_selectors.len(), |
| 265 | self.style_selectors.len(), | 280 | self.style_selectors.len(), |
| 266 | self.exceptions.len(), | 281 | self.exceptions.len(), |
| 267 | self.injected_script, | 282 | self.injected_script.diy_python_repr(), |
| 268 | self.generichide, | 283 | self.generichide.diy_python_repr(), |
| 269 | )) | 284 | )) |
| 270 | } | 285 | } |
| 271 | } | 286 | } |
| @@ -293,6 +308,7 @@ impl PyObjectProtocol for UrlSpecificResources { | |||
| 293 | #[text_signature = "($self, filter_set, optimize)"] | 308 | #[text_signature = "($self, filter_set, optimize)"] |
| 294 | pub struct Engine { | 309 | pub struct Engine { |
| 295 | engine: RustEngine, | 310 | engine: RustEngine, |
| 311 | optimize: bool, | ||
| 296 | } | 312 | } |
| 297 | 313 | ||
| 298 | #[pymethods] | 314 | #[pymethods] |
| @@ -302,7 +318,7 @@ impl Engine { | |||
| 302 | #[args(filter_set, optimize = true)] | 318 | #[args(filter_set, optimize = true)] |
| 303 | pub fn new(filter_set: FilterSet, optimize: bool) -> Self { | 319 | pub fn new(filter_set: FilterSet, optimize: bool) -> Self { |
| 304 | let engine = RustEngine::from_filter_set(filter_set.filter_set, optimize); | 320 | let engine = RustEngine::from_filter_set(filter_set.filter_set, optimize); |
| 305 | Self { engine } | 321 | Self { engine, optimize } |
| 306 | } | 322 | } |
| 307 | 323 | ||
| 308 | /// Check if the given `url`—pointing to a resource of type `request_type`— | 324 | /// Check if the given `url`—pointing to a resource of type `request_type`— |
| @@ -523,3 +539,52 @@ impl Engine { | |||
| 523 | .hidden_class_id_selectors(&classes, &ids, &exceptions)) | 539 | .hidden_class_id_selectors(&classes, &ids, &exceptions)) |
| 524 | } | 540 | } |
| 525 | } | 541 | } |
| 542 | |||
| 543 | #[pyproto] | ||
| 544 | impl PyObjectProtocol for Engine { | ||
| 545 | fn __repr__(&self) -> PyResult<String> { | ||
| 546 | Ok(format!( | ||
| 547 | "Engine<optimize={}>", | ||
| 548 | self.optimize.diy_python_repr() | ||
| 549 | )) | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | /// PyO3 doesn't offer the ability to get the Python representation of a Rust | ||
| 554 | /// object, so we make our own trait. | ||
| 555 | trait DiyPythonRepr { | ||
| 556 | fn diy_python_repr(&self) -> String; | ||
| 557 | } | ||
| 558 | |||
| 559 | impl<T> DiyPythonRepr for Option<T> | ||
| 560 | where | ||
| 561 | T: DiyPythonRepr, | ||
| 562 | { | ||
| 563 | fn diy_python_repr(&self) -> String { | ||
| 564 | match self { | ||
| 565 | None => "None".to_owned(), | ||
| 566 | Some(x) => x.diy_python_repr(), | ||
| 567 | } | ||
| 568 | } | ||
| 569 | } | ||
| 570 | |||
| 571 | impl DiyPythonRepr for String { | ||
| 572 | fn diy_python_repr(&self) -> String { | ||
| 573 | let mut res = format!("{:?}", self); | ||
| 574 | // This is safe to do since we know that `res` will always be of | ||
| 575 | // length >= 2. | ||
| 576 | res.replace_range(0..1, "'"); | ||
| 577 | res.replace_range(res.len() - 1..res.len(), "'"); | ||
| 578 | res | ||
| 579 | } | ||
| 580 | } | ||
| 581 | |||
| 582 | impl DiyPythonRepr for bool { | ||
| 583 | fn diy_python_repr(&self) -> String { | ||
| 584 | if *self { | ||
| 585 | "True".to_owned() | ||
| 586 | } else { | ||
| 587 | "False".to_owned() | ||
| 588 | } | ||
| 589 | } | ||
| 590 | } | ||
diff --git a/tests/test_repr.py b/tests/test_repr.py new file mode 100644 index 0000000..9c1f961 --- /dev/null +++ b/tests/test_repr.py | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | import adblock | ||
| 2 | import re | ||
| 3 | |||
| 4 | |||
| 5 | def assert_acceptable_repr(obj): | ||
| 6 | # Default repr is r"<[A-Za-z]+ object at 0x[0-9a-f]+>" | ||
| 7 | assert "object at" not in repr(obj) | ||
| 8 | assert re.match(r"[A-Z][a-zA-Z]+\(.*\)", repr(obj)) or re.match( | ||
| 9 | r"([A-Z][a-zA-Z]+)?<.*>", repr(obj) | ||
| 10 | ) | ||
| 11 | |||
| 12 | |||
| 13 | def test_has_nondefault_repr(): | ||
| 14 | for b in (True, False): | ||
| 15 | fs = adblock.FilterSet(debug=b) | ||
| 16 | assert_acceptable_repr(fs) | ||
| 17 | assert repr(b) in repr(fs) | ||
| 18 | |||
| 19 | fs.add_filters(["||example.com^"]) | ||
| 20 | |||
| 21 | e = adblock.Engine(fs) | ||
| 22 | assert_acceptable_repr(e) | ||
| 23 | |||
| 24 | result = e.check_network_urls( | ||
| 25 | "https://example.com/picture.png", "https://example.net", "image" | ||
| 26 | ) | ||
| 27 | assert_acceptable_repr(result) | ||
| 28 | assert repr(result) == ( | ||
| 29 | "BlockerResult(matched={}, important={}, redirect={}, exception={}, filter={}, error={})".format( | ||
| 30 | repr(result.matched), | ||
| 31 | repr(result.important), | ||
| 32 | repr(result.redirect), | ||
| 33 | repr(result.exception), | ||
| 34 | repr(result.filter), | ||
| 35 | repr(result.error), | ||
| 36 | ) | ||
| 37 | ) | ||
