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.
- finger2gemtext-0.0.1/PKG-INFO +107 -0
- finger2gemtext-0.0.1/README.md +77 -0
- finger2gemtext-0.0.1/pyproject.toml +111 -0
- finger2gemtext-0.0.1/pyproject.toml.orig +118 -0
- finger2gemtext-0.0.1/src/finger2gemtext/__init__.py +34 -0
- finger2gemtext-0.0.1/src/finger2gemtext/__main__.py +22 -0
- finger2gemtext-0.0.1/src/finger2gemtext/available_services.py +71 -0
- finger2gemtext-0.0.1/src/finger2gemtext/convert.py +37 -0
- finger2gemtext-0.0.1/src/finger2gemtext/finger_filter.py +52 -0
- finger2gemtext-0.0.1/src/finger2gemtext/py.typed +0 -0
- finger2gemtext-0.0.1/src/finger2gemtext/user_lists.py +65 -0
|
@@ -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
|