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
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -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**
Expand Down
20 changes: 17 additions & 3 deletions src/gophermap/exceptions.py
Original file line number Diff line number Diff line change
@@ -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
7 changes: 3 additions & 4 deletions src/gophermap/gopher_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 4 additions & 8 deletions src/gophermap/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

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


Expand All @@ -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."""
Expand Down
20 changes: 14 additions & 6 deletions tests/test_gopher_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


##############################################################################
Expand Down
48 changes: 32 additions & 16 deletions tests/test_gopher_map.py
Original file line number Diff line number Diff line change
@@ -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


##############################################################################
Expand All @@ -23,32 +20,51 @@ 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"
assert gopher_map.items[0].port == 70


##############################################################################
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
Loading