finger2gemtext 0.0.1__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.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: finger2gemtext
3
+ Version: 0.0.1
4
+ Summary: A simple library for converting finger output to Gemtext
5
+ Keywords: converter,Gemini,Gemtext,Hypertext,library,Markup,parser,Small Web,smolweb,finger
6
+ Author: Dave Pearson
7
+ Author-email: Dave Pearson <davep@davep.org>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Text Processing :: Filters
18
+ Classifier: Topic :: Text Processing :: Markup
19
+ Classifier: Topic :: Text Processing
20
+ Classifier: Topic :: Utilities
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.12
23
+ Project-URL: Homepage, https://finger2gemtext.davep.dev/
24
+ Project-URL: Repository, https://github.com/davep/finger2gemtext
25
+ Project-URL: Documentation, https://finger2gemtext.davep.dev/
26
+ Project-URL: Source, https://github.com/davep/finger2gemtext
27
+ Project-URL: Issues, https://github.com/davep/finger2gemtext/issues
28
+ Project-URL: Discussions, https://github.com/davep/finger2gemtext/discussions
29
+ Description-Content-Type: text/markdown
30
+
31
+ # finger2gemtext - A simple library for converting finger output to Gemtext
32
+
33
+ ## Introduction
34
+
35
+ `finger2gemtext` is a small and simple library that provides code for
36
+ converting `finger` responses into [the hypertext markup language of the
37
+ Gemini project](https://geminiprotocol.net/docs/gemtext-specification.gmi).
38
+
39
+ ## Installation
40
+
41
+ `finger2gemtext` is [available from
42
+ pypi](https://pypi.org/project/finger2gemtext/) and can be installed with your
43
+ package installer of choice.
44
+
45
+ With `pip`:
46
+
47
+ ```shell
48
+ pip install finger2gemtext
49
+ ```
50
+
51
+ With `uv`:
52
+
53
+ ```shell
54
+ uv add finger2gemtext
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ The library provides a single main conversion function called
60
+ `finger_to_gemtext`. It is passed a string that is the finger response you
61
+ wish to convert, and the result is a string that is the resulting Gemtext.
62
+
63
+ A very minimal converter might look like:
64
+
65
+ ```python
66
+ import fileinput
67
+ from .convert import finger_to_gemtext
68
+
69
+ def convert() -> None:
70
+ """Parse the input from stdin or files and print the parsed Gemtext."""
71
+ with fileinput.input() as finger_content:
72
+ print(finger_to_gemtext("".join(finger_content)))
73
+ ```
74
+
75
+ While it is primarily intended as a library to be used from other Python
76
+ code, it does contain a simple test command line tool, which can be accessed
77
+ either via the Python `-m` switch, or depending on your environment, via the
78
+ `finger2gemtext` command. For example, if you were to run this command:
79
+
80
+ ```sh
81
+ finger @typed-hole.org | finger2gemtext
82
+ ```
83
+
84
+ you'd get output like this:
85
+
86
+ ```gemtext
87
+ [typed-hole.org]
88
+
89
+ Welcome to the Typed Hole
90
+ Uptime: 14:10:28 up 248 days, 22:12, 0 user, load average: 0.44, 0.34, 0.44
91
+ Users currently logged in: probably julien
92
+
93
+ Available fingers:
94
+
95
+ => /finger/username username: get user infos
96
+ => /finger/feed feed: get my latest toots
97
+ => /finger/lobsters lobsters: get lobste.rs hottest stories
98
+ => /finger/weather weather: get typed-hole.org current weather
99
+ => /finger/temp temp: get typed-hole.org current CPU temperature
100
+ => /finger/cyoa cyoa: finger your own adventure
101
+ => /finger/textfile textfile: read a random textfile from textfiles.com
102
+ => /finger/smog smog: read SMOG e-zine issues
103
+ ```
104
+
105
+ See [the main documentation](https://finger2gemtext.davep.dev/) for the full API.
106
+
107
+ [//]: # (README.md ends here)
@@ -0,0 +1,77 @@
1
+ # finger2gemtext - A simple library for converting finger output to Gemtext
2
+
3
+ ## Introduction
4
+
5
+ `finger2gemtext` is a small and simple library that provides code for
6
+ converting `finger` responses into [the hypertext markup language of the
7
+ Gemini project](https://geminiprotocol.net/docs/gemtext-specification.gmi).
8
+
9
+ ## Installation
10
+
11
+ `finger2gemtext` is [available from
12
+ pypi](https://pypi.org/project/finger2gemtext/) and can be installed with your
13
+ package installer of choice.
14
+
15
+ With `pip`:
16
+
17
+ ```shell
18
+ pip install finger2gemtext
19
+ ```
20
+
21
+ With `uv`:
22
+
23
+ ```shell
24
+ uv add finger2gemtext
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ The library provides a single main conversion function called
30
+ `finger_to_gemtext`. It is passed a string that is the finger response you
31
+ wish to convert, and the result is a string that is the resulting Gemtext.
32
+
33
+ A very minimal converter might look like:
34
+
35
+ ```python
36
+ import fileinput
37
+ from .convert import finger_to_gemtext
38
+
39
+ def convert() -> None:
40
+ """Parse the input from stdin or files and print the parsed Gemtext."""
41
+ with fileinput.input() as finger_content:
42
+ print(finger_to_gemtext("".join(finger_content)))
43
+ ```
44
+
45
+ While it is primarily intended as a library to be used from other Python
46
+ code, it does contain a simple test command line tool, which can be accessed
47
+ either via the Python `-m` switch, or depending on your environment, via the
48
+ `finger2gemtext` command. For example, if you were to run this command:
49
+
50
+ ```sh
51
+ finger @typed-hole.org | finger2gemtext
52
+ ```
53
+
54
+ you'd get output like this:
55
+
56
+ ```gemtext
57
+ [typed-hole.org]
58
+
59
+ Welcome to the Typed Hole
60
+ Uptime: 14:10:28 up 248 days, 22:12, 0 user, load average: 0.44, 0.34, 0.44
61
+ Users currently logged in: probably julien
62
+
63
+ Available fingers:
64
+
65
+ => /finger/username username: get user infos
66
+ => /finger/feed feed: get my latest toots
67
+ => /finger/lobsters lobsters: get lobste.rs hottest stories
68
+ => /finger/weather weather: get typed-hole.org current weather
69
+ => /finger/temp temp: get typed-hole.org current CPU temperature
70
+ => /finger/cyoa cyoa: finger your own adventure
71
+ => /finger/textfile textfile: read a random textfile from textfiles.com
72
+ => /finger/smog smog: read SMOG e-zine issues
73
+ ```
74
+
75
+ See [the main documentation](https://finger2gemtext.davep.dev/) for the full API.
76
+
77
+ [//]: # (README.md ends here)
@@ -0,0 +1,111 @@
1
+ [project]
2
+ name = "finger2gemtext"
3
+ version = "0.0.1"
4
+ description = "A simple library for converting finger output to Gemtext"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = []
8
+ license = "MIT"
9
+ keywords = [
10
+ "converter",
11
+ "Gemini",
12
+ "Gemtext",
13
+ "Hypertext",
14
+ "library",
15
+ "Markup",
16
+ "parser",
17
+ "Small Web",
18
+ "smolweb",
19
+ "finger",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Topic :: Software Development :: Libraries",
30
+ "Topic :: Text Processing :: Filters",
31
+ "Topic :: Text Processing :: Markup",
32
+ "Topic :: Text Processing",
33
+ "Topic :: Utilities",
34
+ "Typing :: Typed",
35
+ ]
36
+
37
+ [[project.authors]]
38
+ name = "Dave Pearson"
39
+ email = "davep@davep.org"
40
+
41
+ [project.urls]
42
+ Homepage = "https://finger2gemtext.davep.dev/"
43
+ Repository = "https://github.com/davep/finger2gemtext"
44
+ Documentation = "https://finger2gemtext.davep.dev/"
45
+ Source = "https://github.com/davep/finger2gemtext"
46
+ Issues = "https://github.com/davep/finger2gemtext/issues"
47
+ Discussions = "https://github.com/davep/finger2gemtext/discussions"
48
+
49
+ [project.scripts]
50
+ finger2gemtext = "finger2gemtext.__main__:convert"
51
+
52
+ [build-system]
53
+ requires = ["uv_build>=0.12.9,<0.13.0"]
54
+ build-backend = "uv_build"
55
+
56
+ [[tool.uv.index]]
57
+ name = "testpypi"
58
+ url = "https://test.pypi.org/simple/"
59
+ publish-url = "https://test.pypi.org/legacy/"
60
+ explicit = true
61
+
62
+ [tool.pyright]
63
+ venvPath = "."
64
+ venv = ".venv"
65
+ exclude = [".venv"]
66
+
67
+ [tool.ruff.lint]
68
+ select = [
69
+ "E",
70
+ "F",
71
+ "UP",
72
+ "B",
73
+ "SIM",
74
+ "I",
75
+ ]
76
+
77
+ [tool.ruff.lint.pycodestyle]
78
+ max-line-length = 120
79
+
80
+ [tool.coverage.run]
81
+ omit = ["tests/*"]
82
+
83
+ [tool.coverage.report]
84
+ exclude_lines = [
85
+ "pragma: no cover",
86
+ "def __repr__",
87
+ "def _repr_props",
88
+ "raise AssertionError",
89
+ "raise NotImplementedError",
90
+ "if __name__ == .__main__.:",
91
+ "if TYPE_CHECKING:",
92
+ 'class .*\bProtocol\):',
93
+ '@(abc\.)?abstractmethod',
94
+ ]
95
+
96
+ [dependency-groups]
97
+ dev = [
98
+ "codespell>=2.4.2",
99
+ "mypy>=2.1.0",
100
+ "pre-commit>=4.6.0",
101
+ "ruff>=0.15.17",
102
+ ]
103
+ docs = [
104
+ "mkdocs>=1.6.1,<2",
105
+ "mkdocs-material>=9.7.6",
106
+ "mkdocstrings[python]>=1.0.4",
107
+ ]
108
+ test = [
109
+ "pytest>=9.1.0",
110
+ "pytest-cov>=7.1.0",
111
+ ]
@@ -0,0 +1,118 @@
1
+ [project]
2
+ name = "finger2gemtext"
3
+ version = "0.0.1"
4
+ description = "A simple library for converting finger output to Gemtext"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Dave Pearson", email = "davep@davep.org" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = []
11
+ license = "MIT"
12
+ keywords = [
13
+ "converter",
14
+ "Gemini",
15
+ "Gemtext",
16
+ "Hypertext",
17
+ "library",
18
+ "Markup",
19
+ "parser",
20
+ "Small Web",
21
+ "smolweb",
22
+ "finger",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Programming Language :: Python :: 3.14",
32
+ "Topic :: Software Development :: Libraries",
33
+ "Topic :: Text Processing :: Filters",
34
+ "Topic :: Text Processing :: Markup",
35
+ "Topic :: Text Processing",
36
+ "Topic :: Utilities",
37
+ "Typing :: Typed",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://finger2gemtext.davep.dev/"
42
+ Repository = "https://github.com/davep/finger2gemtext"
43
+ Documentation = "https://finger2gemtext.davep.dev/"
44
+ Source = "https://github.com/davep/finger2gemtext"
45
+ Issues = "https://github.com/davep/finger2gemtext/issues"
46
+ Discussions = "https://github.com/davep/finger2gemtext/discussions"
47
+
48
+ [project.scripts]
49
+ finger2gemtext = "finger2gemtext.__main__:convert"
50
+
51
+ [build-system]
52
+ requires = ["uv_build>=0.12.9,<0.13.0"]
53
+ build-backend = "uv_build"
54
+
55
+ [[tool.uv.index]]
56
+ name = "testpypi"
57
+ url = "https://test.pypi.org/simple/"
58
+ publish-url = "https://test.pypi.org/legacy/"
59
+ explicit = true
60
+
61
+ [tool.pyright]
62
+ venvPath="."
63
+ venv=".venv"
64
+ exclude=[".venv"]
65
+
66
+ [tool.ruff.lint]
67
+ select = [
68
+ # pycodestyle
69
+ "E",
70
+ # Pyflakes
71
+ "F",
72
+ # pyupgrade
73
+ "UP",
74
+ # flake8-bugbear
75
+ "B",
76
+ # flake8-simplify
77
+ "SIM",
78
+ # isort
79
+ "I",
80
+ ]
81
+
82
+ [tool.ruff.lint.pycodestyle]
83
+ max-line-length = 120
84
+
85
+ [tool.coverage.run]
86
+ omit = [
87
+ "tests/*"
88
+ ]
89
+
90
+ [tool.coverage.report]
91
+ exclude_lines = [
92
+ "pragma: no cover",
93
+ "def __repr__",
94
+ "def _repr_props",
95
+ "raise AssertionError",
96
+ "raise NotImplementedError",
97
+ "if __name__ == .__main__.:",
98
+ "if TYPE_CHECKING:",
99
+ "class .*\\bProtocol\\):",
100
+ "@(abc\\.)?abstractmethod",
101
+ ]
102
+
103
+ [dependency-groups]
104
+ dev = [
105
+ "codespell>=2.4.2",
106
+ "mypy>=2.1.0",
107
+ "pre-commit>=4.6.0",
108
+ "ruff>=0.15.17",
109
+ ]
110
+ docs = [
111
+ "mkdocs>=1.6.1,<2",
112
+ "mkdocs-material>=9.7.6",
113
+ "mkdocstrings[python]>=1.0.4",
114
+ ]
115
+ test = [
116
+ "pytest>=9.1.0",
117
+ "pytest-cov>=7.1.0",
118
+ ]
@@ -0,0 +1,34 @@
1
+ """A simple finger to Gemtext converter."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from importlib.metadata import version
6
+
7
+ ######################################################################
8
+ # Main library information.
9
+ __author__ = "Dave Pearson"
10
+ __copyright__ = "Copyright 2026, Dave Pearson"
11
+ __credits__ = ["Dave Pearson"]
12
+ __maintainer__ = "Dave Pearson"
13
+ __email__ = "davep@davep.org"
14
+ __version__: str = version("finger2gemtext")
15
+ __licence__ = "MIT"
16
+
17
+ ##############################################################################
18
+ # Local imports.
19
+ from .available_services import AvailableServicesFilter
20
+ from .convert import finger_to_gemtext
21
+ from .finger_filter import FingerFilter
22
+ from .user_lists import UserListFilter
23
+
24
+ ##############################################################################
25
+ # Exports.
26
+ __all__ = [
27
+ "AvailableServicesFilter",
28
+ "finger_to_gemtext",
29
+ "FingerFilter",
30
+ "UserListFilter",
31
+ ]
32
+
33
+
34
+ ### __init__.py ends here
@@ -0,0 +1,22 @@
1
+ ##############################################################################
2
+ # Python imports.
3
+ import fileinput
4
+
5
+ ##############################################################################
6
+ # Local imports.
7
+ from .convert import finger_to_gemtext
8
+
9
+
10
+ ##############################################################################
11
+ def convert() -> None:
12
+ """Parse the input from stdin or files and print the parsed Gemtext."""
13
+ with fileinput.input() as finger_content:
14
+ print(finger_to_gemtext("".join(finger_content)))
15
+
16
+
17
+ ##############################################################################
18
+ if __name__ == "__main__":
19
+ convert()
20
+
21
+
22
+ ### __main__.py ends here
@@ -0,0 +1,71 @@
1
+ """Provides support code for working with available-services-type responses."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from re import Pattern, compile
6
+ from typing import Final
7
+
8
+ ##############################################################################
9
+ # Local imports.
10
+ from .finger_filter import FingerFilter
11
+
12
+ ##############################################################################
13
+ _TELL: Final[str] = "available fingers"
14
+ """String to look for in the Finger content to identify available services responses."""
15
+ _SERVICE: Final[Pattern[str]] = compile(r"^(?P<name>\S+?):?\s{2,}.*$")
16
+
17
+
18
+ ##############################################################################
19
+ class AvailableServicesFilter(FingerFilter):
20
+ """Filter for Finger available services responses."""
21
+
22
+ def __init__(self, peekable_lines: int = 20) -> None:
23
+ """Initialise the filter class.
24
+
25
+ Args:
26
+ peekable_lines: The number of lines to peek at when deciding the content type.
27
+ """
28
+ super().__init__(peekable_lines)
29
+
30
+ def can_likely_convert(self, finger_content: str) -> bool:
31
+ """Check if the filter can likely convert the given Finger content.
32
+
33
+ Args:
34
+ finger_content: The Finger content to check.
35
+
36
+ Returns:
37
+ [`True`][True] if the filter can likely convert the content;
38
+ [`False`][False] otherwise.
39
+ """
40
+ return any(
41
+ _TELL in line.lower() for line in self.peekable_lines(finger_content)
42
+ )
43
+
44
+ def to_gemtext(self, finger_content: str) -> str:
45
+ """Convert the given finger content to Gemtext.
46
+
47
+ Args:
48
+ finger_content: The Finger content to convert.
49
+
50
+ Returns:
51
+ The converted Gemtext content.
52
+ """
53
+ gemtext_lines: list[str] = []
54
+ gather = gemtext_lines.append
55
+ converting = False
56
+ found_services = False
57
+ for line in finger_content.splitlines():
58
+ if _TELL in line.lower():
59
+ gather(line)
60
+ converting = True
61
+ continue
62
+ if converting and (service := _SERVICE.match(line)) is not None:
63
+ gather(f"=> /finger/{service['name']} {line}")
64
+ found_services = True
65
+ else:
66
+ gather(line)
67
+ gather("")
68
+ return "\n".join(gemtext_lines) if found_services else finger_content
69
+
70
+
71
+ ### _available_services.py ends here
@@ -0,0 +1,37 @@
1
+ """Provides a simple finger to Gemtext converter."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from collections.abc import Iterable
6
+
7
+ ##############################################################################
8
+ # Local imports.
9
+ from .available_services import AvailableServicesFilter
10
+ from .finger_filter import FingerFilter
11
+ from .user_lists import UserListFilter
12
+
13
+
14
+ ##############################################################################
15
+ def finger_to_gemtext(
16
+ finger_content: str, additional_filters: Iterable[type[FingerFilter]] | None = None
17
+ ) -> str:
18
+ """Convert Finger content to Gemtext.
19
+
20
+ Args:
21
+ finger_content: The Finger content to convert.
22
+
23
+ Returns:
24
+ The converted Gemtext content.
25
+ """
26
+ filters: Iterable[type[FingerFilter]] = (
27
+ UserListFilter,
28
+ AvailableServicesFilter,
29
+ *(additional_filters or ()),
30
+ )
31
+ for current_filter in (candidate() for candidate in filters):
32
+ if current_filter.can_likely_convert(finger_content):
33
+ return current_filter.to_gemtext(finger_content)
34
+ return finger_content
35
+
36
+
37
+ ### convert.py ends here
@@ -0,0 +1,52 @@
1
+ """Provides the base finger filter class."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Iterator
7
+
8
+
9
+ ##############################################################################
10
+ class FingerFilter(ABC):
11
+ """Base class for finger filters."""
12
+
13
+ def __init__(self, peekable_lines: int = 10) -> None:
14
+ """Initialise the filter class.
15
+
16
+ Args:
17
+ peekable_lines: The number of lines to peek at when deciding the content type.
18
+ """
19
+ self._peekable_lines = max(1, peekable_lines)
20
+ """The number of lines to peek at when deciding the content type."""
21
+
22
+ def peekable_lines(self, finger_content: str) -> Iterator[str]:
23
+ """The number of lines to peek at when deciding the content type."""
24
+ for line_number, line in enumerate(finger_content.splitlines()):
25
+ if line_number >= self._peekable_lines:
26
+ break
27
+ yield line
28
+
29
+ @abstractmethod
30
+ def can_likely_convert(self, finger_content: str) -> bool:
31
+ """Check if the filter can likely convert the given Finger content.
32
+
33
+ Args:
34
+ finger_content: The Finger content to check.
35
+
36
+ Returns:
37
+ True if the filter can likely convert the content; False otherwise.
38
+ """
39
+
40
+ @abstractmethod
41
+ def to_gemtext(self, finger_content: str) -> str:
42
+ """Convert the given finger content to Gemtext.
43
+
44
+ Args:
45
+ finger_content: The Finger content to convert.
46
+
47
+ Returns:
48
+ The converted Gemtext content.
49
+ """
50
+
51
+
52
+ ### finger_filter.py ends here
File without changes
@@ -0,0 +1,65 @@
1
+ """Provides support code for working with user list responses."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from re import Pattern, compile
6
+ from typing import Final
7
+
8
+ ##############################################################################
9
+ # Local imports.
10
+ from .finger_filter import FingerFilter
11
+
12
+ ##############################################################################
13
+ _USER_LIST_HEADER: Final[Pattern[str]] = compile(r"^Login\s+Name\s+Login Time\s*$")
14
+ """Regular expression to match the header of a user list in Finger output."""
15
+ _USER_ENTRY: Final[Pattern[str]] = compile(r"^(?P<username>\S+)\s{2,}.*$")
16
+ """Regular expression to match a user entry in Finger output."""
17
+
18
+
19
+ ##############################################################################
20
+ class UserListFilter(FingerFilter):
21
+ """Filter for Finger user list responses."""
22
+
23
+ def can_likely_convert(self, finger_content: str) -> bool:
24
+ """Check if the filter can likely convert the given Finger content.
25
+
26
+ Args:
27
+ finger_content: The Finger content to check.
28
+
29
+ Returns:
30
+ [`True`][True] if the filter can likely convert the content;
31
+ [`False`][False] otherwise.
32
+ """
33
+ return any(
34
+ _USER_LIST_HEADER.match(line)
35
+ for line in self.peekable_lines(finger_content)
36
+ )
37
+
38
+ def to_gemtext(self, finger_content: str) -> str:
39
+ """Convert the given finger content to Gemtext.
40
+
41
+ Args:
42
+ finger_content: The Finger content to convert.
43
+
44
+ Returns:
45
+ The converted Gemtext content.
46
+ """
47
+ gemtext_lines: list[str] = []
48
+ gather = gemtext_lines.append
49
+ converting = False
50
+ found_users = False
51
+ for line in finger_content.splitlines():
52
+ if _USER_LIST_HEADER.match(line):
53
+ gather(line)
54
+ converting = True
55
+ continue
56
+ if converting and (user := _USER_ENTRY.match(line)) is not None:
57
+ gather(f"=> /finger/{user.group('username')} {line}")
58
+ found_users = True
59
+ else:
60
+ gather(line)
61
+ gather("")
62
+ return "\n".join(gemtext_lines) if found_users else finger_content
63
+
64
+
65
+ ### _user_lists.py ends here