aprsd 1.0.0__py3-none-any.whl → 3.4.1__py3-none-any.whl

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.
Files changed (141) hide show
  1. aprsd/__init__.py +6 -4
  2. aprsd/cli_helper.py +151 -0
  3. aprsd/client/__init__.py +13 -0
  4. aprsd/client/aprsis.py +132 -0
  5. aprsd/client/base.py +105 -0
  6. aprsd/client/drivers/__init__.py +0 -0
  7. aprsd/client/drivers/aprsis.py +224 -0
  8. aprsd/client/drivers/fake.py +73 -0
  9. aprsd/client/drivers/kiss.py +119 -0
  10. aprsd/client/factory.py +88 -0
  11. aprsd/client/fake.py +48 -0
  12. aprsd/client/kiss.py +103 -0
  13. aprsd/client/stats.py +38 -0
  14. aprsd/cmds/__init__.py +0 -0
  15. aprsd/cmds/completion.py +22 -0
  16. aprsd/cmds/dev.py +162 -0
  17. aprsd/cmds/fetch_stats.py +156 -0
  18. aprsd/cmds/healthcheck.py +86 -0
  19. aprsd/cmds/list_plugins.py +319 -0
  20. aprsd/cmds/listen.py +230 -0
  21. aprsd/cmds/send_message.py +174 -0
  22. aprsd/cmds/server.py +142 -0
  23. aprsd/cmds/webchat.py +681 -0
  24. aprsd/conf/__init__.py +56 -0
  25. aprsd/conf/client.py +131 -0
  26. aprsd/conf/common.py +302 -0
  27. aprsd/conf/log.py +65 -0
  28. aprsd/conf/opts.py +80 -0
  29. aprsd/conf/plugin_common.py +191 -0
  30. aprsd/conf/plugin_email.py +105 -0
  31. aprsd/exception.py +13 -0
  32. aprsd/log/__init__.py +0 -0
  33. aprsd/log/log.py +138 -0
  34. aprsd/main.py +104 -867
  35. aprsd/messaging.py +4 -0
  36. aprsd/packets/__init__.py +12 -0
  37. aprsd/packets/collector.py +56 -0
  38. aprsd/packets/core.py +823 -0
  39. aprsd/packets/log.py +143 -0
  40. aprsd/packets/packet_list.py +116 -0
  41. aprsd/packets/seen_list.py +54 -0
  42. aprsd/packets/tracker.py +109 -0
  43. aprsd/packets/watch_list.py +122 -0
  44. aprsd/plugin.py +475 -284
  45. aprsd/plugin_utils.py +86 -0
  46. aprsd/plugins/__init__.py +0 -0
  47. aprsd/plugins/email.py +709 -0
  48. aprsd/plugins/fortune.py +61 -0
  49. aprsd/plugins/location.py +179 -0
  50. aprsd/plugins/notify.py +61 -0
  51. aprsd/plugins/ping.py +31 -0
  52. aprsd/plugins/time.py +115 -0
  53. aprsd/plugins/version.py +31 -0
  54. aprsd/plugins/weather.py +405 -0
  55. aprsd/stats/__init__.py +20 -0
  56. aprsd/stats/app.py +49 -0
  57. aprsd/stats/collector.py +38 -0
  58. aprsd/threads/__init__.py +11 -0
  59. aprsd/threads/aprsd.py +119 -0
  60. aprsd/threads/keep_alive.py +124 -0
  61. aprsd/threads/log_monitor.py +121 -0
  62. aprsd/threads/registry.py +56 -0
  63. aprsd/threads/rx.py +354 -0
  64. aprsd/threads/stats.py +44 -0
  65. aprsd/threads/tx.py +255 -0
  66. aprsd/utils/__init__.py +163 -0
  67. aprsd/utils/counter.py +51 -0
  68. aprsd/utils/json.py +80 -0
  69. aprsd/utils/objectstore.py +123 -0
  70. aprsd/utils/ring_buffer.py +40 -0
  71. aprsd/utils/trace.py +180 -0
  72. aprsd/web/__init__.py +0 -0
  73. aprsd/web/admin/__init__.py +0 -0
  74. aprsd/web/admin/static/css/index.css +84 -0
  75. aprsd/web/admin/static/css/prism.css +4 -0
  76. aprsd/web/admin/static/css/tabs.css +35 -0
  77. aprsd/web/admin/static/images/Untitled.png +0 -0
  78. aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
  79. aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
  80. aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
  81. aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
  82. aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
  83. aprsd/web/admin/static/js/charts.js +235 -0
  84. aprsd/web/admin/static/js/echarts.js +465 -0
  85. aprsd/web/admin/static/js/logs.js +26 -0
  86. aprsd/web/admin/static/js/main.js +231 -0
  87. aprsd/web/admin/static/js/prism.js +12 -0
  88. aprsd/web/admin/static/js/send-message.js +114 -0
  89. aprsd/web/admin/static/js/tabs.js +28 -0
  90. aprsd/web/admin/templates/index.html +196 -0
  91. aprsd/web/chat/static/css/chat.css +115 -0
  92. aprsd/web/chat/static/css/index.css +66 -0
  93. aprsd/web/chat/static/css/style.css.map +1 -0
  94. aprsd/web/chat/static/css/tabs.css +41 -0
  95. aprsd/web/chat/static/css/upstream/bootstrap.min.css +6 -0
  96. aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
  97. aprsd/web/chat/static/css/upstream/google-fonts.css +23 -0
  98. aprsd/web/chat/static/css/upstream/jquery-ui.css +1311 -0
  99. aprsd/web/chat/static/css/upstream/jquery.toast.css +28 -0
  100. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
  101. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
  102. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
  103. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
  104. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
  105. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
  106. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
  107. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
  108. aprsd/web/chat/static/images/Untitled.png +0 -0
  109. aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
  110. aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
  111. aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
  112. aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
  113. aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
  114. aprsd/web/chat/static/images/globe.svg +3 -0
  115. aprsd/web/chat/static/js/gps.js +84 -0
  116. aprsd/web/chat/static/js/main.js +45 -0
  117. aprsd/web/chat/static/js/send-message.js +585 -0
  118. aprsd/web/chat/static/js/tabs.js +28 -0
  119. aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +7 -0
  120. aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +2 -0
  121. aprsd/web/chat/static/js/upstream/jquery-ui.min.js +13 -0
  122. aprsd/web/chat/static/js/upstream/jquery.toast.js +374 -0
  123. aprsd/web/chat/static/js/upstream/semantic.min.js +11 -0
  124. aprsd/web/chat/static/js/upstream/socket.io.min.js +7 -0
  125. aprsd/web/chat/templates/index.html +139 -0
  126. aprsd/wsgi.py +315 -0
  127. aprsd-3.4.1.dist-info/AUTHORS +13 -0
  128. aprsd-3.4.1.dist-info/LICENSE +175 -0
  129. aprsd-3.4.1.dist-info/METADATA +799 -0
  130. aprsd-3.4.1.dist-info/RECORD +134 -0
  131. {aprsd-1.0.0.dist-info → aprsd-3.4.1.dist-info}/WHEEL +1 -1
  132. aprsd-3.4.1.dist-info/entry_points.txt +8 -0
  133. aprsd/fake_aprs.py +0 -83
  134. aprsd/utils.py +0 -166
  135. aprsd-1.0.0.dist-info/AUTHORS +0 -6
  136. aprsd-1.0.0.dist-info/METADATA +0 -181
  137. aprsd-1.0.0.dist-info/RECORD +0 -13
  138. aprsd-1.0.0.dist-info/entry_points.txt +0 -4
  139. aprsd-1.0.0.dist-info/pbr.json +0 -1
  140. /aprsd/{fuzzyclock.py → utils/fuzzyclock.py} +0 -0
  141. {aprsd-1.0.0.dist-info → aprsd-3.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,156 @@
1
+ # Fetch active stats from a remote running instance of aprsd admin web interface.
2
+ import logging
3
+
4
+ import click
5
+ from oslo_config import cfg
6
+ import requests
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ # local imports here
11
+ import aprsd
12
+ from aprsd import cli_helper
13
+ from aprsd.main import cli
14
+
15
+
16
+ # setup the global logger
17
+ # log.basicConfig(level=log.DEBUG) # level=10
18
+ LOG = logging.getLogger("APRSD")
19
+ CONF = cfg.CONF
20
+
21
+
22
+ @cli.command()
23
+ @cli_helper.add_options(cli_helper.common_options)
24
+ @click.option(
25
+ "--host", type=str,
26
+ default=None,
27
+ help="IP address of the remote aprsd admin web ui fetch stats from.",
28
+ )
29
+ @click.option(
30
+ "--port", type=int,
31
+ default=None,
32
+ help="Port of the remote aprsd web admin interface to fetch stats from.",
33
+ )
34
+ @click.pass_context
35
+ @cli_helper.process_standard_options
36
+ def fetch_stats(ctx, host, port):
37
+ """Fetch stats from a APRSD admin web interface."""
38
+ console = Console()
39
+ console.print(f"APRSD Fetch-Stats started version: {aprsd.__version__}")
40
+
41
+ CONF.log_opt_values(LOG, logging.DEBUG)
42
+ if not host:
43
+ host = CONF.admin.web_ip
44
+ if not port:
45
+ port = CONF.admin.web_port
46
+
47
+ msg = f"Fetching stats from {host}:{port}"
48
+ console.print(msg)
49
+ with console.status(msg):
50
+ response = requests.get(f"http://{host}:{port}/stats", timeout=120)
51
+ if not response:
52
+ console.print(
53
+ f"Failed to fetch stats from {host}:{port}?",
54
+ style="bold red",
55
+ )
56
+ return
57
+
58
+ stats = response.json()
59
+ if not stats:
60
+ console.print(
61
+ f"Failed to fetch stats from aprsd admin ui at {host}:{port}",
62
+ style="bold red",
63
+ )
64
+ return
65
+
66
+ aprsd_title = (
67
+ "APRSD "
68
+ f"[bold cyan]v{stats['APRSDStats']['version']}[/] "
69
+ f"Callsign [bold green]{stats['APRSDStats']['callsign']}[/] "
70
+ f"Uptime [bold yellow]{stats['APRSDStats']['uptime']}[/]"
71
+ )
72
+
73
+ console.rule(f"Stats from {host}:{port}")
74
+ console.print("\n\n")
75
+ console.rule(aprsd_title)
76
+
77
+ # Show the connection to APRS
78
+ # It can be a connection to an APRS-IS server or a local TNC via KISS or KISSTCP
79
+ if "aprs-is" in stats:
80
+ title = f"APRS-IS Connection {stats['APRSClientStats']['server_string']}"
81
+ table = Table(title=title)
82
+ table.add_column("Key")
83
+ table.add_column("Value")
84
+ for key, value in stats["APRSClientStats"].items():
85
+ table.add_row(key, value)
86
+ console.print(table)
87
+
88
+ threads_table = Table(title="Threads")
89
+ threads_table.add_column("Name")
90
+ threads_table.add_column("Alive?")
91
+ for name, alive in stats["APRSDThreadList"].items():
92
+ threads_table.add_row(name, str(alive))
93
+
94
+ console.print(threads_table)
95
+
96
+ packet_totals = Table(title="Packet Totals")
97
+ packet_totals.add_column("Key")
98
+ packet_totals.add_column("Value")
99
+ packet_totals.add_row("Total Received", str(stats["PacketList"]["rx"]))
100
+ packet_totals.add_row("Total Sent", str(stats["PacketList"]["tx"]))
101
+ console.print(packet_totals)
102
+
103
+ # Show each of the packet types
104
+ packets_table = Table(title="Packets By Type")
105
+ packets_table.add_column("Packet Type")
106
+ packets_table.add_column("TX")
107
+ packets_table.add_column("RX")
108
+ for key, value in stats["PacketList"]["packets"].items():
109
+ packets_table.add_row(key, str(value["tx"]), str(value["rx"]))
110
+
111
+ console.print(packets_table)
112
+
113
+ if "plugins" in stats:
114
+ count = len(stats["PluginManager"])
115
+ plugins_table = Table(title=f"Plugins ({count})")
116
+ plugins_table.add_column("Plugin")
117
+ plugins_table.add_column("Enabled")
118
+ plugins_table.add_column("Version")
119
+ plugins_table.add_column("TX")
120
+ plugins_table.add_column("RX")
121
+ plugins = stats["PluginManager"]
122
+ for key, value in plugins.items():
123
+ plugins_table.add_row(
124
+ key,
125
+ str(plugins[key]["enabled"]),
126
+ plugins[key]["version"],
127
+ str(plugins[key]["tx"]),
128
+ str(plugins[key]["rx"]),
129
+ )
130
+
131
+ console.print(plugins_table)
132
+
133
+ seen_list = stats.get("SeenList")
134
+
135
+ if seen_list:
136
+ count = len(seen_list)
137
+ seen_table = Table(title=f"Seen List ({count})")
138
+ seen_table.add_column("Callsign")
139
+ seen_table.add_column("Message Count")
140
+ seen_table.add_column("Last Heard")
141
+ for key, value in seen_list.items():
142
+ seen_table.add_row(key, str(value["count"]), value["last"])
143
+
144
+ console.print(seen_table)
145
+
146
+ watch_list = stats.get("WatchList")
147
+
148
+ if watch_list:
149
+ count = len(watch_list)
150
+ watch_table = Table(title=f"Watch List ({count})")
151
+ watch_table.add_column("Callsign")
152
+ watch_table.add_column("Last Heard")
153
+ for key, value in watch_list.items():
154
+ watch_table.add_row(key, value["last"])
155
+
156
+ console.print(watch_table)
@@ -0,0 +1,86 @@
1
+ #
2
+ # Used to fetch the stats url and determine if
3
+ # aprsd server is 'healthy'
4
+ #
5
+ #
6
+ # python included libs
7
+ import datetime
8
+ import logging
9
+ import sys
10
+
11
+ import click
12
+ from oslo_config import cfg
13
+ from rich.console import Console
14
+
15
+ import aprsd
16
+ from aprsd import cli_helper
17
+ from aprsd import conf # noqa
18
+ # local imports here
19
+ from aprsd.main import cli
20
+ from aprsd.threads import stats as stats_threads
21
+
22
+
23
+ # setup the global logger
24
+ # log.basicConfig(level=log.DEBUG) # level=10
25
+ CONF = cfg.CONF
26
+ LOG = logging.getLogger("APRSD")
27
+ console = Console()
28
+
29
+
30
+ @cli.command()
31
+ @cli_helper.add_options(cli_helper.common_options)
32
+ @click.option(
33
+ "--timeout",
34
+ show_default=True,
35
+ default=3,
36
+ help="How long to wait for healtcheck url to come back",
37
+ )
38
+ @click.pass_context
39
+ @cli_helper.process_standard_options
40
+ def healthcheck(ctx, timeout):
41
+ """Check the health of the running aprsd server."""
42
+ ver_str = f"APRSD HealthCheck version: {aprsd.__version__}"
43
+ console.log(ver_str)
44
+
45
+ with console.status(ver_str):
46
+ try:
47
+ stats_obj = stats_threads.StatsStore()
48
+ stats_obj.load()
49
+ stats = stats_obj.data
50
+ # console.print(stats)
51
+ except Exception as ex:
52
+ console.log(f"Failed to load stats: '{ex}'")
53
+ sys.exit(-1)
54
+ else:
55
+ now = datetime.datetime.now()
56
+ if not stats:
57
+ console.log("No stats from aprsd")
58
+ sys.exit(-1)
59
+
60
+ email_stats = stats.get("EmailStats")
61
+ if email_stats:
62
+ email_thread_last_update = email_stats["last_check_time"]
63
+
64
+ if email_thread_last_update != "never":
65
+ d = now - email_thread_last_update
66
+ max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
67
+ max_delta = datetime.timedelta(**max_timeout)
68
+ if d > max_delta:
69
+ console.log(f"Email thread is very old! {d}")
70
+ sys.exit(-1)
71
+
72
+ client_stats = stats.get("APRSClientStats")
73
+ if not client_stats:
74
+ console.log("No APRSClientStats")
75
+ sys.exit(-1)
76
+ else:
77
+ aprsis_last_update = client_stats["server_keepalive"]
78
+ d = now - aprsis_last_update
79
+ max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
80
+ max_delta = datetime.timedelta(**max_timeout)
81
+ if d > max_delta:
82
+ LOG.error(f"APRS-IS last update is very old! {d}")
83
+ sys.exit(-1)
84
+
85
+ console.log("OK")
86
+ sys.exit(0)
@@ -0,0 +1,319 @@
1
+ import fnmatch
2
+ import importlib
3
+ import inspect
4
+ import logging
5
+ import os
6
+ import pkgutil
7
+ import re
8
+ import sys
9
+ from traceback import print_tb
10
+ from urllib.parse import urljoin
11
+
12
+ from bs4 import BeautifulSoup
13
+ import click
14
+ import requests
15
+ from rich.console import Console
16
+ from rich.table import Table
17
+ from rich.text import Text
18
+ from thesmuggler import smuggle
19
+
20
+ from aprsd import cli_helper
21
+ from aprsd import plugin as aprsd_plugin
22
+ from aprsd.main import cli
23
+ from aprsd.plugins import (
24
+ email, fortune, location, notify, ping, time, version, weather,
25
+ )
26
+
27
+
28
+ LOG = logging.getLogger("APRSD")
29
+ PYPI_URL = "https://pypi.org/search/"
30
+
31
+
32
+ def onerror(name):
33
+ print(f"Error importing module {name}")
34
+ type, value, traceback = sys.exc_info()
35
+ print_tb(traceback)
36
+
37
+
38
+ def is_plugin(obj):
39
+ for c in inspect.getmro(obj):
40
+ if issubclass(c, aprsd_plugin.APRSDPluginBase):
41
+ return True
42
+
43
+ return False
44
+
45
+
46
+ def plugin_type(obj):
47
+ for c in inspect.getmro(obj):
48
+ if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
49
+ return "RegexCommand"
50
+ if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
51
+ return "WatchList"
52
+ if issubclass(c, aprsd_plugin.APRSDPluginBase):
53
+ return "APRSDPluginBase"
54
+
55
+ return "Unknown"
56
+
57
+
58
+ def walk_package(package):
59
+ return pkgutil.walk_packages(
60
+ package.__path__,
61
+ package.__name__ + ".",
62
+ onerror=onerror,
63
+ )
64
+
65
+
66
+ def get_module_info(package_name, module_name, module_path):
67
+ if not os.path.exists(module_path):
68
+ return None
69
+
70
+ dir_path = os.path.realpath(module_path)
71
+ pattern = "*.py"
72
+
73
+ obj_list = []
74
+
75
+ for path, _subdirs, files in os.walk(dir_path):
76
+ for name in files:
77
+ if fnmatch.fnmatch(name, pattern):
78
+ module = smuggle(f"{path}/{name}")
79
+ for mem_name, obj in inspect.getmembers(module):
80
+ if inspect.isclass(obj) and is_plugin(obj):
81
+ obj_list.append(
82
+ {
83
+ "package": package_name,
84
+ "name": mem_name, "obj": obj,
85
+ "version": obj.version,
86
+ "path": f"{'.'.join([module_name, obj.__name__])}",
87
+ },
88
+ )
89
+
90
+ return obj_list
91
+
92
+
93
+ def _get_installed_aprsd_items():
94
+ # installed plugins
95
+ plugins = {}
96
+ extensions = {}
97
+ for finder, name, ispkg in pkgutil.iter_modules():
98
+ if name.startswith("aprsd_"):
99
+ print(f"Found aprsd_ module: {name}")
100
+ if ispkg:
101
+ module = importlib.import_module(name)
102
+ pkgs = walk_package(module)
103
+ for pkg in pkgs:
104
+ pkg_info = get_module_info(module.__name__, pkg.name, module.__path__[0])
105
+ if "plugin" in name:
106
+ plugins[name] = pkg_info
107
+ elif "extension" in name:
108
+ extensions[name] = pkg_info
109
+ return plugins, extensions
110
+
111
+
112
+ def get_installed_plugins():
113
+ # installed plugins
114
+ plugins, extensions = _get_installed_aprsd_items()
115
+ return plugins
116
+
117
+
118
+ def get_installed_extensions():
119
+ # installed plugins
120
+ plugins, extensions = _get_installed_aprsd_items()
121
+ return extensions
122
+
123
+
124
+ def show_built_in_plugins(console):
125
+ modules = [email, fortune, location, notify, ping, time, version, weather]
126
+ plugins = []
127
+
128
+ for module in modules:
129
+ entries = inspect.getmembers(module, inspect.isclass)
130
+ for entry in entries:
131
+ cls = entry[1]
132
+ if issubclass(cls, aprsd_plugin.APRSDPluginBase):
133
+ info = {
134
+ "name": cls.__qualname__,
135
+ "path": f"{cls.__module__}.{cls.__qualname__}",
136
+ "version": cls.version,
137
+ "docstring": cls.__doc__,
138
+ "short_desc": cls.short_description,
139
+ }
140
+
141
+ if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
142
+ info["command_regex"] = cls.command_regex
143
+ info["type"] = "RegexCommand"
144
+
145
+ if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
146
+ info["type"] = "WatchList"
147
+
148
+ plugins.append(info)
149
+
150
+ plugins = sorted(plugins, key=lambda i: i["name"])
151
+
152
+ table = Table(
153
+ title="[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]",
154
+ )
155
+ table.add_column("Plugin Name", style="cyan", no_wrap=True)
156
+ table.add_column("Info", style="bold yellow")
157
+ table.add_column("Type", style="bold green")
158
+ table.add_column("Plugin Path", style="bold blue")
159
+ for entry in plugins:
160
+ table.add_row(entry["name"], entry["short_desc"], entry["type"], entry["path"])
161
+
162
+ console.print(table)
163
+
164
+
165
+ def _get_pypi_packages():
166
+ query = "aprsd"
167
+ snippets = []
168
+ s = requests.Session()
169
+ for page in range(1, 3):
170
+ params = {"q": query, "page": page}
171
+ r = s.get(PYPI_URL, params=params)
172
+ soup = BeautifulSoup(r.text, "html.parser")
173
+ snippets += soup.select('a[class*="snippet"]')
174
+ if not hasattr(s, "start_url"):
175
+ s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
176
+
177
+ return snippets
178
+
179
+
180
+ def show_pypi_plugins(installed_plugins, console):
181
+ snippets = _get_pypi_packages()
182
+
183
+ title = Text.assemble(
184
+ ("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
185
+ ("Install any of the following plugins with\n", "bold yellow"),
186
+ ("'pip install ", "bold white"),
187
+ ("<Plugin Package Name>'", "cyan"),
188
+ )
189
+
190
+ table = Table(title=title)
191
+ table.add_column("Plugin Package Name", style="cyan", no_wrap=True)
192
+ table.add_column("Description", style="yellow")
193
+ table.add_column("Version", style="yellow", justify="center")
194
+ table.add_column("Released", style="bold green", justify="center")
195
+ table.add_column("Installed?", style="red", justify="center")
196
+ for snippet in snippets:
197
+ link = urljoin(PYPI_URL, snippet.get("href"))
198
+ package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
199
+ version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
200
+ created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
201
+ description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
202
+ emoji = ":open_file_folder:"
203
+
204
+ if "aprsd-" not in package or "-plugin" not in package:
205
+ continue
206
+
207
+ under = package.replace("-", "_")
208
+ if under in installed_plugins:
209
+ installed = "Yes"
210
+ else:
211
+ installed = "No"
212
+
213
+ table.add_row(
214
+ f"[link={link}]{emoji}[/link] {package}",
215
+ description, version, created, installed,
216
+ )
217
+
218
+ console.print("\n")
219
+ console.print(table)
220
+
221
+
222
+ def show_pypi_extensions(installed_extensions, console):
223
+ snippets = _get_pypi_packages()
224
+
225
+ title = Text.assemble(
226
+ ("Pypi.org APRSD Installable Extension Packages\n\n", "bold magenta"),
227
+ ("Install any of the following extensions by running\n", "bold yellow"),
228
+ ("'pip install ", "bold white"),
229
+ ("<Plugin Package Name>'", "cyan"),
230
+ )
231
+ table = Table(title=title)
232
+ table.add_column("Extension Package Name", style="cyan", no_wrap=True)
233
+ table.add_column("Description", style="yellow")
234
+ table.add_column("Version", style="yellow", justify="center")
235
+ table.add_column("Released", style="bold green", justify="center")
236
+ table.add_column("Installed?", style="red", justify="center")
237
+ for snippet in snippets:
238
+ link = urljoin(PYPI_URL, snippet.get("href"))
239
+ package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
240
+ version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
241
+ created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
242
+ description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
243
+ emoji = ":open_file_folder:"
244
+
245
+ if "aprsd-" not in package or "-extension" not in package:
246
+ continue
247
+
248
+ under = package.replace("-", "_")
249
+ if under in installed_extensions:
250
+ installed = "Yes"
251
+ else:
252
+ installed = "No"
253
+
254
+ table.add_row(
255
+ f"[link={link}]{emoji}[/link] {package}",
256
+ description, version, created, installed,
257
+ )
258
+
259
+ console.print("\n")
260
+ console.print(table)
261
+
262
+
263
+ def show_installed_plugins(installed_plugins, console):
264
+ if not installed_plugins:
265
+ return
266
+
267
+ table = Table(
268
+ title="[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]",
269
+ )
270
+ table.add_column("Package Name", style=" bold white", no_wrap=True)
271
+ table.add_column("Plugin Name", style="cyan", no_wrap=True)
272
+ table.add_column("Version", style="yellow", justify="center")
273
+ table.add_column("Type", style="bold green")
274
+ table.add_column("Plugin Path", style="bold blue")
275
+ for name in installed_plugins:
276
+ for plugin in installed_plugins[name]:
277
+ table.add_row(
278
+ name.replace("_", "-"),
279
+ plugin["name"],
280
+ plugin["version"],
281
+ plugin_type(plugin["obj"]),
282
+ plugin["path"],
283
+ )
284
+
285
+ console.print("\n")
286
+ console.print(table)
287
+
288
+
289
+ @cli.command()
290
+ @cli_helper.add_options(cli_helper.common_options)
291
+ @click.pass_context
292
+ @cli_helper.process_standard_options_no_config
293
+ def list_plugins(ctx):
294
+ """List the built in plugins available to APRSD."""
295
+ console = Console()
296
+
297
+ with console.status("Show Built-in Plugins") as status:
298
+ show_built_in_plugins(console)
299
+
300
+ status.update("Fetching pypi.org plugins")
301
+ installed_plugins = get_installed_plugins()
302
+ show_pypi_plugins(installed_plugins, console)
303
+
304
+ status.update("Looking for installed APRSD plugins")
305
+ show_installed_plugins(installed_plugins, console)
306
+
307
+
308
+ @cli.command()
309
+ @cli_helper.add_options(cli_helper.common_options)
310
+ @click.pass_context
311
+ @cli_helper.process_standard_options_no_config
312
+ def list_extensions(ctx):
313
+ """List the built in plugins available to APRSD."""
314
+ console = Console()
315
+
316
+ with console.status("Show APRSD Extensions") as status:
317
+ status.update("Fetching pypi.org APRSD Extensions")
318
+ installed_extensions = get_installed_extensions()
319
+ show_pypi_extensions(installed_extensions, console)