Skip to content

Commit 17302d6

Browse files
authored
🔀 Merge pull request #12 from davep/add-strict-mode
Add an optional strict mode
2 parents 7584f1e + 0009660 commit 17302d6

6 files changed

Lines changed: 77 additions & 7 deletions

File tree

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- Removed the exceptions from the library.
88
([#10](https://github.com/davep/gophermap/pull/10))
9+
- Added an optional strict mode (and, in doing so, added an exception back).
10+
([#12](https://github.com/davep/gophermap/pull/12))
911

1012
## v0.2.0
1113

src/gophermap/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
##############################################################################
1818
# Local imports.
19+
from .exceptions import GopherMapError
1920
from .gopher_map import GopherMap
2021
from .item import GopherItem
2122
from .item_type import ItemType
@@ -25,6 +26,7 @@
2526
__all__ = [
2627
"GopherItem",
2728
"GopherMap",
29+
"GopherMapError",
2830
"ItemType",
2931
]
3032

src/gophermap/exceptions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Provides exceptions for the Gopher map."""
2+
3+
4+
##############################################################################
5+
class GopherMapError(Exception):
6+
"""Base class for Gopher map errors."""
7+
8+
9+
##############################################################################
10+
class EmptyMap(GopherMapError):
11+
"""Raised when a Gopher map is empty."""
12+
13+
14+
##############################################################################
15+
class NoFields(GopherMapError):
16+
"""Raised when a Gopher item has no fields."""
17+
18+
19+
##############################################################################
20+
class UnknownItemType(GopherMapError):
21+
"""Raised when a Gopher item has an unknown type."""
22+
23+
24+
### exceptions.py ends here

src/gophermap/gopher_map.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
##############################################################################
1010
# Local imports.
11+
from .exceptions import EmptyMap
1112
from .item import GopherItem
1213

1314
##############################################################################
@@ -19,17 +20,19 @@
1920
class GopherMap:
2021
"""A class for parsing and holding a Gopher map."""
2122

22-
def __init__(self, map_text: str) -> None:
23+
def __init__(self, map_text: str, strict: bool = False) -> None:
2324
"""Initialise the Gopher map.
2425
2526
Args:
2627
map_text: The text of the Gopher map.
28+
strict: Whether to be strict about parsing the Gopher map.
2729
"""
2830
self._raw = map_text
2931
"""The raw text of the Gopher map."""
32+
self._strict = strict
33+
"""Whether to be strict about parsing the Gopher map."""
3034

31-
@staticmethod
32-
def _parse_map(map_text: str) -> Iterator[GopherItem]:
35+
def _parse_map(self, map_text: str) -> Iterator[GopherItem]:
3336
"""Parse the Gopher map text into a list of Gopher items.
3437
3538
Args:
@@ -38,10 +41,12 @@ def _parse_map(map_text: str) -> Iterator[GopherItem]:
3841
Yields:
3942
Gopher items.
4043
"""
44+
if self._strict and not map_text:
45+
raise EmptyMap("Gopher map is empty")
4146
for line in map_text.splitlines():
4247
if line == EOF:
4348
break
44-
yield GopherItem(line)
49+
yield GopherItem(line, self._strict)
4550

4651
@property
4752
def raw(self) -> str:
@@ -50,7 +55,13 @@ def raw(self) -> str:
5055

5156
@cached_property
5257
def items(self) -> tuple[GopherItem, ...]:
53-
"""The list of Gopher items in the map."""
58+
"""The list of Gopher items in the map.
59+
60+
Raises:
61+
EmptyMap: If the map is empty and strict mode is enabled.
62+
NoFields: If the line is missing a tab character and strict mode is enabled.
63+
UnknownItemType: If the item type is unknown and strict mode is enabled.
64+
"""
5465
return tuple(self._parse_map(self._raw))
5566

5667

src/gophermap/item.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22

33
##############################################################################
44
# Local imports.
5+
from .exceptions import NoFields, UnknownItemType
56
from .item_type import ItemType
67

78

89
##############################################################################
910
class GopherItem:
1011
"""A class for holding an item in the Gopher map."""
1112

12-
def __init__(self, line: str) -> None:
13+
def __init__(self, line: str, strict: bool = False) -> None:
1314
"""Initialise the Gopher item.
1415
1516
Args:
1617
line: The line of text from the Gopher map.
18+
strict: Whether to be strict about parsing the Gopher item.
19+
20+
Raises:
21+
NoFields: If the line is missing a tab character and strict mode is enabled.
22+
UnknownItemType: If the item type is unknown and strict mode is enabled.
1723
"""
1824
self._raw = line
1925
"""The raw text of the Gopher item."""
@@ -29,6 +35,12 @@ def __init__(self, line: str) -> None:
2935
"""The host of the Gopher item."""
3036
self._port = int(fields[3]) if len(fields) > 3 and fields[3].isdigit() else 70
3137
"""The port of the Gopher item."""
38+
# If we're in strict mode, let's do some harsh checks.
39+
if strict:
40+
if "\t" not in line:
41+
raise NoFields(f"Line is missing a tab character: {line!r}")
42+
if self._type is ItemType.UNKNOWN:
43+
raise UnknownItemType(f"Unknown item type: {self._type!r}")
3244

3345
@property
3446
def raw(self) -> str:

tests/test_gopher_map.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""Tests for the GopherMap class."""
22

3+
##############################################################################
4+
# Pytest imports.
5+
from pytest import mark, raises
6+
37
##############################################################################
48
# Local imports.
5-
from gophermap import GopherMap
9+
from gophermap import GopherMap, GopherMapError
610
from gophermap.item_type import ItemType
711

812

@@ -67,4 +71,19 @@ def test_allow_lines_without_tabs() -> None:
6771
assert gopher_map.items[0].port == 70
6872

6973

74+
##############################################################################
75+
@mark.parametrize(
76+
"test_map",
77+
[
78+
"",
79+
"Test\r\n.\r\n",
80+
"!Hello\tworld\tlocalhost\r\n.\r\n",
81+
],
82+
)
83+
def test_strict_on_bad_map(test_map: str) -> None:
84+
"""Test that strict mode raises an error on a bad map."""
85+
with raises(GopherMapError):
86+
_ = GopherMap(test_map, strict=True).items
87+
88+
7089
### test_gopher_map.py ends here

0 commit comments

Comments
 (0)