diff --git a/ChangeLog.md b/ChangeLog.md index be2390e..b0e7951 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,12 @@ # GopherMap ChangeLog +## Unreleased + +**Released: 2026-07-27** + +- Make parsing far more relaxed. Empty lines also become empty `i`nfo lines. + ([#7](https://github.com/davep/gophermap/pull/7)) + ## v0.1.1 **Released: 2026-07-27** diff --git a/src/gophermap/exceptions.py b/src/gophermap/exceptions.py index 31ab79a..5bc985e 100644 --- a/src/gophermap/exceptions.py +++ b/src/gophermap/exceptions.py @@ -1,14 +1,28 @@ -"""Exceptions for the library.""" +"""Exceptions for the library. + +Note: + This module is now deprecated and will be removed in a future release. +""" ############################################################################## class GopherMapError(Exception): - """Base exception for all errors raised by the GopherMap library.""" + """Base exception for all errors raised by the GopherMap library. + + Note: + This exception class is now deprecated and will be removed in a + future release. + """ ############################################################################## class NoFields(GopherMapError): - """Raised when a Gopher item has no fields.""" + """Raised when a Gopher item has no fields. + + Note: + This exception class is now deprecated and will be removed in a + future release. + """ ### exceptions.py ends here diff --git a/src/gophermap/gopher_map.py b/src/gophermap/gopher_map.py index 8b41d26..2502904 100644 --- a/src/gophermap/gopher_map.py +++ b/src/gophermap/gopher_map.py @@ -38,11 +38,10 @@ def _parse_map(map_text: str) -> Iterator[GopherItem]: Yields: Gopher items. """ - for line in map_text.splitlines(keepends=True): - if line.strip() == EOF: + for line in map_text.splitlines(): + if line == EOF: break - if line.strip(): - yield GopherItem(line) + yield GopherItem(line) @property def raw(self) -> str: diff --git a/src/gophermap/item.py b/src/gophermap/item.py index 6e0e5c4..b677b50 100644 --- a/src/gophermap/item.py +++ b/src/gophermap/item.py @@ -2,7 +2,6 @@ ############################################################################## # Local imports. -from .exceptions import NoFields from .item_type import ItemType @@ -16,16 +15,13 @@ def __init__(self, line: str) -> None: Args: line: The line of text from the Gopher map. """ - if not line: - raise NoFields("The Gopher item line is empty.") - if "\t" not in line: - raise NoFields(f"The Gopher item line has no tab characters: {line!r}") self._raw = line """The raw text of the Gopher item.""" - fields = line.rstrip("\r\n").split("\t") - self._type = ItemType(fields[0][0] or ItemType.INFO) + if not (fields := line.rstrip("\r\n").split("\t"))[0]: + fields[0] = ItemType.INFO.value + self._type = ItemType(fields[0][0]) """The type of the Gopher item.""" - self._display_text = fields[0][1:] if len(fields) > 0 else "" + self._display_text = fields[0][1:] """The display text of the Gopher item.""" self._selector = fields[1] if len(fields) > 1 else "" """The selector of the Gopher item.""" diff --git a/tests/test_gopher_item.py b/tests/test_gopher_item.py index 485de77..2c3722d 100644 --- a/tests/test_gopher_item.py +++ b/tests/test_gopher_item.py @@ -2,18 +2,26 @@ ############################################################################## # Pytest imports. -from pytest import raises +from pytest import mark ############################################################################## # Local imports. -from gophermap import GopherItem, NoFields +from gophermap import GopherItem +from gophermap.item_type import ItemType ############################################################################## -def test_empty_line() -> None: - """Test that an empty line raises NoFields.""" - with raises(NoFields): - _ = GopherItem("") +@mark.parametrize( + "line", + [ + "", + "\r\n", + "\t\r\n", + ], +) +def test_empty_lines_become_info(line: str) -> None: + """Test that empty lines become INFO items.""" + assert GopherItem(line).type is ItemType.INFO ############################################################################## diff --git a/tests/test_gopher_map.py b/tests/test_gopher_map.py index 4097cf2..8d04fd6 100644 --- a/tests/test_gopher_map.py +++ b/tests/test_gopher_map.py @@ -1,12 +1,9 @@ """Tests for the GopherMap class.""" -############################################################################## -# Pytest imports. -from pytest import raises - ############################################################################## # Local imports. -from gophermap import GopherMap, NoFields +from gophermap import GopherMap +from gophermap.item_type import ItemType ############################################################################## @@ -23,20 +20,13 @@ def test_eof_only_map() -> None: assert gopher_map.items == () -############################################################################## -def test_no_fields() -> None: - """Test that a Gopher map with no fields raises NoFields.""" - with raises(NoFields): - _ = GopherMap("x").items - - ############################################################################## def test_valid_map() -> None: """Test that a valid Gopher map is parsed correctly.""" gopher_map = GopherMap(raw := "iHello\tworld\tlocalhost\t70\r\n.\r\n") assert raw == gopher_map.raw assert len(gopher_map.items) == 1 - assert gopher_map.items[0].type.name == "INFO" + assert gopher_map.items[0].type is ItemType.INFO assert gopher_map.items[0].display_text == "Hello" assert gopher_map.items[0].selector == "world" assert gopher_map.items[0].host == "localhost" @@ -44,11 +34,37 @@ def test_valid_map() -> None: ############################################################################## -def test_skip_empty_lines() -> None: - """Test that empty lines are skipped.""" +def test_empty_lines_become_info() -> None: + """Test that empty lines become INFO items.""" gopher_map = GopherMap(raw := "\r\n\r\n.\r\n") assert raw == gopher_map.raw - assert len(gopher_map.items) == 0 + assert len(gopher_map.items) == 2 + assert gopher_map.items[0].type is ItemType.INFO + assert gopher_map.items[0].display_text == "" + assert gopher_map.items[0].selector == "" + assert gopher_map.items[0].host == "" + assert gopher_map.items[0].port == 70 + assert gopher_map.items[1].type is ItemType.INFO + assert gopher_map.items[1].display_text == "" + assert gopher_map.items[1].selector == "" + assert gopher_map.items[1].host == "" + assert gopher_map.items[1].port == 70 + + +############################################################################## +def test_allow_lines_without_tabs() -> None: + """Test that lines without tabs are allowed. + + https://github.com/davep/rogallo/discussions/241 + """ + gopher_map = GopherMap(raw := "iHello\r\n.\r\n") + assert raw == gopher_map.raw + assert len(gopher_map.items) == 1 + assert gopher_map.items[0].type is ItemType.INFO + assert gopher_map.items[0].display_text == "Hello" + assert gopher_map.items[0].selector == "" + assert gopher_map.items[0].host == "" + assert gopher_map.items[0].port == 70 ### test_gopher_map.py ends here