gophermap 0.2.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gophermap
3
- Version: 0.2.0
3
+ Version: 1.1.0
4
4
  Summary: A simple library for parsng Gophermaps
5
5
  Keywords: Gopher,Gophermap,Hypertext,library,Markup,parser,Small Web,smolweb
6
6
  Author: Dave Pearson
7
7
  Author-email: Dave Pearson <davep@davep.org>
8
8
  License-Expression: MIT
9
- Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3 :: Only
12
12
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,104 @@
1
+ [project]
2
+ name = "gophermap"
3
+ version = "1.1.0"
4
+ description = "A simple library for parsng Gophermaps"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = []
8
+ license = "MIT"
9
+ keywords = [
10
+ "Gopher",
11
+ "Gophermap",
12
+ "Hypertext",
13
+ "library",
14
+ "Markup",
15
+ "parser",
16
+ "Small Web",
17
+ "smolweb",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 5 - Production/Stable",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Software Development :: Libraries",
28
+ "Typing :: Typed",
29
+ ]
30
+
31
+ [[project.authors]]
32
+ name = "Dave Pearson"
33
+ email = "davep@davep.org"
34
+
35
+ [project.urls]
36
+ Homepage = "https://gophermap.davep.dev/"
37
+ Repository = "https://github.com/davep/gophermap"
38
+ Documentation = "https://gophermap.davep.dev/"
39
+ Source = "https://github.com/davep/gophermap"
40
+ Issues = "https://github.com/davep/gophermap/issues"
41
+ Discussions = "https://github.com/davep/gophermap/discussions"
42
+
43
+ [project.scripts]
44
+ gophermap = "gophermap.__main__:parse_input"
45
+
46
+ [build-system]
47
+ requires = ["uv_build>=0.11.32,<0.12.0"]
48
+ build-backend = "uv_build"
49
+
50
+ [[tool.uv.index]]
51
+ name = "testpypi"
52
+ url = "https://test.pypi.org/simple/"
53
+ publish-url = "https://test.pypi.org/legacy/"
54
+ explicit = true
55
+
56
+ [tool.pyright]
57
+ venvPath = "."
58
+ venv = ".venv"
59
+ exclude = [".venv"]
60
+
61
+ [tool.ruff.lint]
62
+ select = [
63
+ "E",
64
+ "F",
65
+ "UP",
66
+ "B",
67
+ "SIM",
68
+ "I",
69
+ ]
70
+
71
+ [tool.ruff.lint.pycodestyle]
72
+ max-line-length = 120
73
+
74
+ [tool.coverage.run]
75
+ omit = ["tests/*"]
76
+
77
+ [tool.coverage.report]
78
+ exclude_lines = [
79
+ "pragma: no cover",
80
+ "def __repr__",
81
+ "raise AssertionError",
82
+ "raise NotImplementedError",
83
+ "if __name__ == .__main__.:",
84
+ "if TYPE_CHECKING:",
85
+ 'class .*\bProtocol\):',
86
+ '@(abc\.)?abstractmethod',
87
+ ]
88
+
89
+ [dependency-groups]
90
+ dev = [
91
+ "codespell>=2.4.3",
92
+ "mypy>=2.3.0",
93
+ "pre-commit>=4.6.0",
94
+ "ruff>=0.15.22",
95
+ ]
96
+ docs = [
97
+ "mkdocs>=1.6.1,<2",
98
+ "mkdocs-material>=9.7.6",
99
+ "mkdocstrings[python]>=1.0.4",
100
+ ]
101
+ test = [
102
+ "pytest>=9.1.1",
103
+ "pytest-cov>=7.1.0",
104
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gophermap"
3
- version = "0.2.0"
3
+ version = "1.1.0"
4
4
  description = "A simple library for parsng Gophermaps"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -20,7 +20,7 @@ keywords = [
20
20
  "smolweb",
21
21
  ]
22
22
  classifiers = [
23
- "Development Status :: 3 - Alpha",
23
+ "Development Status :: 5 - Production/Stable",
24
24
  "Operating System :: OS Independent",
25
25
  "Programming Language :: Python :: 3 :: Only",
26
26
  "Programming Language :: Python :: 3",
@@ -16,7 +16,7 @@ __licence__ = "MIT"
16
16
 
17
17
  ##############################################################################
18
18
  # Local imports.
19
- from .exceptions import GopherMapError, NoFields
19
+ from .exceptions import GopherMapError
20
20
  from .gopher_map import GopherMap
21
21
  from .item import GopherItem
22
22
  from .item_type import ItemType
@@ -28,7 +28,6 @@ __all__ = [
28
28
  "GopherMap",
29
29
  "GopherMapError",
30
30
  "ItemType",
31
- "NoFields",
32
31
  ]
33
32
 
34
33
  ### __init__.py ends here
@@ -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
@@ -0,0 +1,117 @@
1
+ """Provides a class for parsing and holding a Gopher map."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from collections.abc import Iterator
6
+ from functools import cached_property
7
+ from typing import Final
8
+
9
+ ##############################################################################
10
+ # Local imports.
11
+ from .exceptions import EmptyMap, GopherMapError
12
+ from .item import GopherItem
13
+ from .item_type import ItemType
14
+
15
+ ##############################################################################
16
+ EOF: Final[str] = "."
17
+ """The EOF marker for a Gopher map."""
18
+
19
+
20
+ ##############################################################################
21
+ class GopherMap:
22
+ """A class for parsing and holding a Gopher map."""
23
+
24
+ def __init__(self, map_text: str, strict: bool = False) -> None:
25
+ """Initialise the Gopher map.
26
+
27
+ Args:
28
+ map_text: The text of the Gopher map.
29
+ strict: Whether to be strict about parsing the Gopher map.
30
+ """
31
+ self._raw = map_text
32
+ """The raw text of the Gopher map."""
33
+ self._strict = strict
34
+ """Whether to be strict about parsing the Gopher map."""
35
+
36
+ def _parse_map(self, map_text: str) -> Iterator[GopherItem]:
37
+ """Parse the Gopher map text into a list of Gopher items.
38
+
39
+ Args:
40
+ map_text: The text of the Gopher map.
41
+
42
+ Yields:
43
+ Gopher items.
44
+ """
45
+ if self._strict and not map_text:
46
+ raise EmptyMap("Gopher map is empty")
47
+ for line in map_text.splitlines():
48
+ if line == EOF:
49
+ break
50
+ yield GopherItem(line, self._strict)
51
+
52
+ @property
53
+ def raw(self) -> str:
54
+ """The raw text of the Gopher map."""
55
+ return self._raw
56
+
57
+ @cached_property
58
+ def items(self) -> tuple[GopherItem, ...]:
59
+ """The list of Gopher items in the map.
60
+
61
+ Raises:
62
+ EmptyMap: If the map is empty and strict mode is enabled.
63
+ NoFields: If the line is missing a tab character and strict mode is enabled.
64
+ UnknownItemType: If the item type is unknown and strict mode is enabled.
65
+ """
66
+ return tuple(self._parse_map(self._raw))
67
+
68
+ @cached_property
69
+ def has_error(self) -> bool:
70
+ """Whether the Gopher map contains an error item.
71
+
72
+ Raises:
73
+ EmptyMap: If the map is empty and strict mode is enabled.
74
+ NoFields: If the line is missing a tab character and strict mode is enabled.
75
+ UnknownItemType: If the item type is unknown and strict mode is enabled.
76
+
77
+ Note:
78
+ The mentioned exceptions will be raised on accessing this
79
+ property, if in strict mode. If you wish to check for errors
80
+ without raising exceptions, use the [`is_likely_error` class
81
+ method][gophermap.GopherMap.is_likely_error], to test instead.
82
+ """
83
+ return any(item.type is ItemType.ERROR for item in self.items)
84
+
85
+ @classmethod
86
+ def is_likely_a_map(cls, text: str) -> bool:
87
+ """Determine if the text is likely a valid Gopher map.
88
+
89
+ Args:
90
+ text: The text of the Gopher map.
91
+
92
+ Returns:
93
+ True if the text is likely a valid Gopher map, False otherwise.
94
+ """
95
+ try:
96
+ _ = cls(text, strict=True).items
97
+ except GopherMapError:
98
+ return False
99
+ return True
100
+
101
+ @classmethod
102
+ def is_likely_error(cls, text: str) -> bool:
103
+ """Determine if the text is likely a Gopher error message.
104
+
105
+ Args:
106
+ text: The text to test.
107
+
108
+ Returns:
109
+ True if the text is likely a Gopher error message, False otherwise.
110
+ """
111
+ try:
112
+ return cls(text, strict=True).has_error
113
+ except GopherMapError:
114
+ return False
115
+
116
+
117
+ ### gopher_map.py ends here
@@ -2,6 +2,7 @@
2
2
 
3
3
  ##############################################################################
4
4
  # Local imports.
5
+ from .exceptions import NoFields, UnknownItemType
5
6
  from .item_type import ItemType
6
7
 
7
8
 
@@ -9,11 +10,16 @@ from .item_type import ItemType
9
10
  class GopherItem:
10
11
  """A class for holding an item in the Gopher map."""
11
12
 
12
- def __init__(self, line: str) -> None:
13
+ def __init__(self, line: str, strict: bool = False) -> None:
13
14
  """Initialise the Gopher item.
14
15
 
15
16
  Args:
16
17
  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.
17
23
  """
18
24
  self._raw = line
19
25
  """The raw text of the Gopher item."""
@@ -29,6 +35,12 @@ class GopherItem:
29
35
  """The host of the Gopher item."""
30
36
  self._port = int(fields[3]) if len(fields) > 3 and fields[3].isdigit() else 70
31
37
  """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}")
32
44
 
33
45
  @property
34
46
  def raw(self) -> str:
@@ -45,8 +45,12 @@ class ItemType(StrEnum):
45
45
  """A document file."""
46
46
  AUDIO = "s"
47
47
  """An audio file."""
48
+ PNG = "p"
49
+ """A PNG image."""
48
50
  PDF = "P"
49
51
  """A PDF file."""
52
+ RTF = "r"
53
+ """A rich text format file."""
50
54
  XML = "X"
51
55
  """An XML file."""
52
56
  UNKNOWN = "unknown"
@@ -92,6 +96,8 @@ class ItemType(StrEnum):
92
96
  ItemType.AUDIO: "application/octet-stream",
93
97
  ItemType.PDF: "application/pdf",
94
98
  ItemType.XML: "application/xml",
99
+ ItemType.PNG: "image/png",
100
+ ItemType.RTF: "application/rtf",
95
101
  }.get(self, "application/x-gopher-unknown")
96
102
 
97
103
 
@@ -1,28 +0,0 @@
1
- """Exceptions for the library.
2
-
3
- Note:
4
- This module is now deprecated and will be removed in a future release.
5
- """
6
-
7
-
8
- ##############################################################################
9
- class GopherMapError(Exception):
10
- """Base exception for all errors raised by the GopherMap library.
11
-
12
- Note:
13
- This exception class is now deprecated and will be removed in a
14
- future release.
15
- """
16
-
17
-
18
- ##############################################################################
19
- class NoFields(GopherMapError):
20
- """Raised when a Gopher item has no fields.
21
-
22
- Note:
23
- This exception class is now deprecated and will be removed in a
24
- future release.
25
- """
26
-
27
-
28
- ### exceptions.py ends here
@@ -1,57 +0,0 @@
1
- """Provides a class for parsing and holding a Gopher map."""
2
-
3
- ##############################################################################
4
- # Python imports.
5
- from collections.abc import Iterator
6
- from functools import cached_property
7
- from typing import Final
8
-
9
- ##############################################################################
10
- # Local imports.
11
- from .item import GopherItem
12
-
13
- ##############################################################################
14
- EOF: Final[str] = "."
15
- """The EOF marker for a Gopher map."""
16
-
17
-
18
- ##############################################################################
19
- class GopherMap:
20
- """A class for parsing and holding a Gopher map."""
21
-
22
- def __init__(self, map_text: str) -> None:
23
- """Initialise the Gopher map.
24
-
25
- Args:
26
- map_text: The text of the Gopher map.
27
- """
28
- self._raw = map_text
29
- """The raw text of the Gopher map."""
30
-
31
- @staticmethod
32
- def _parse_map(map_text: str) -> Iterator[GopherItem]:
33
- """Parse the Gopher map text into a list of Gopher items.
34
-
35
- Args:
36
- map_text: The text of the Gopher map.
37
-
38
- Yields:
39
- Gopher items.
40
- """
41
- for line in map_text.splitlines():
42
- if line == EOF:
43
- break
44
- yield GopherItem(line)
45
-
46
- @property
47
- def raw(self) -> str:
48
- """The raw text of the Gopher map."""
49
- return self._raw
50
-
51
- @cached_property
52
- def items(self) -> tuple[GopherItem, ...]:
53
- """The list of Gopher items in the map."""
54
- return tuple(self._parse_map(self._raw))
55
-
56
-
57
- ### gopher_map.py ends here
File without changes