Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

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

## v0.2.0

Expand Down
2 changes: 2 additions & 0 deletions src/gophermap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

##############################################################################
# Local imports.
from .exceptions import GopherMapError
from .gopher_map import GopherMap
from .item import GopherItem
from .item_type import ItemType
Expand All @@ -25,6 +26,7 @@
__all__ = [
"GopherItem",
"GopherMap",
"GopherMapError",
"ItemType",
]

Expand Down
24 changes: 24 additions & 0 deletions src/gophermap/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Provides exceptions for the Gopher map."""


##############################################################################
class GopherMapError(Exception):
"""Base class for Gopher map errors."""


##############################################################################
class EmptyMap(GopherMapError):
"""Raised when a Gopher map is empty."""


##############################################################################
class NoFields(GopherMapError):
"""Raised when a Gopher item has no fields."""


##############################################################################
class UnknownItemType(GopherMapError):
"""Raised when a Gopher item has an unknown type."""


### exceptions.py ends here
21 changes: 16 additions & 5 deletions src/gophermap/gopher_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

##############################################################################
# Local imports.
from .exceptions import EmptyMap
from .item import GopherItem

##############################################################################
Expand All @@ -19,17 +20,19 @@
class GopherMap:
"""A class for parsing and holding a Gopher map."""

def __init__(self, map_text: str) -> None:
def __init__(self, map_text: str, strict: bool = False) -> None:
"""Initialise the Gopher map.

Args:
map_text: The text of the Gopher map.
strict: Whether to be strict about parsing the Gopher map.
"""
self._raw = map_text
"""The raw text of the Gopher map."""
self._strict = strict
"""Whether to be strict about parsing the Gopher map."""

@staticmethod
def _parse_map(map_text: str) -> Iterator[GopherItem]:
def _parse_map(self, map_text: str) -> Iterator[GopherItem]:
"""Parse the Gopher map text into a list of Gopher items.

Args:
Expand All @@ -38,10 +41,12 @@ def _parse_map(map_text: str) -> Iterator[GopherItem]:
Yields:
Gopher items.
"""
if self._strict and not map_text:
raise EmptyMap("Gopher map is empty")
for line in map_text.splitlines():
if line == EOF:
break
yield GopherItem(line)
yield GopherItem(line, self._strict)

@property
def raw(self) -> str:
Expand All @@ -50,7 +55,13 @@ def raw(self) -> str:

@cached_property
def items(self) -> tuple[GopherItem, ...]:
"""The list of Gopher items in the map."""
"""The list of Gopher items in the map.

Raises:
EmptyMap: If the map is empty and strict mode is enabled.
NoFields: If the line is missing a tab character and strict mode is enabled.
UnknownItemType: If the item type is unknown and strict mode is enabled.
"""
return tuple(self._parse_map(self._raw))


Expand Down
14 changes: 13 additions & 1 deletion src/gophermap/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

##############################################################################
# Local imports.
from .exceptions import NoFields, UnknownItemType
from .item_type import ItemType


##############################################################################
class GopherItem:
"""A class for holding an item in the Gopher map."""

def __init__(self, line: str) -> None:
def __init__(self, line: str, strict: bool = False) -> None:
"""Initialise the Gopher item.

Args:
line: The line of text from the Gopher map.
strict: Whether to be strict about parsing the Gopher item.

Raises:
NoFields: If the line is missing a tab character and strict mode is enabled.
UnknownItemType: If the item type is unknown and strict mode is enabled.
"""
self._raw = line
"""The raw text of the Gopher item."""
Expand All @@ -29,6 +35,12 @@ def __init__(self, line: str) -> None:
"""The host of the Gopher item."""
self._port = int(fields[3]) if len(fields) > 3 and fields[3].isdigit() else 70
"""The port of the Gopher item."""
# If we're in strict mode, let's do some harsh checks.
if strict:
if "\t" not in line:
raise NoFields(f"Line is missing a tab character: {line!r}")
if self._type is ItemType.UNKNOWN:
raise UnknownItemType(f"Unknown item type: {self._type!r}")

@property
def raw(self) -> str:
Expand Down
21 changes: 20 additions & 1 deletion tests/test_gopher_map.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Tests for the GopherMap class."""

##############################################################################
# Pytest imports.
from pytest import mark, raises

##############################################################################
# Local imports.
from gophermap import GopherMap
from gophermap import GopherMap, GopherMapError
from gophermap.item_type import ItemType


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


##############################################################################
@mark.parametrize(
"test_map",
[
"",
"Test\r\n.\r\n",
"!Hello\tworld\tlocalhost\r\n.\r\n",
],
)
def test_strict_on_bad_map(test_map: str) -> None:
"""Test that strict mode raises an error on a bad map."""
with raises(GopherMapError):
_ = GopherMap(test_map, strict=True).items


### test_gopher_map.py ends here
Loading