lockss-turtles 0.6.0.dev20__tar.gz → 0.6.0.dev22__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.
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/PKG-INFO +2 -2
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/README.rst +1 -1
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/pyproject.toml +1 -1
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/src/lockss/turtles/__init__.py +4 -1
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/src/lockss/turtles/cli.py +12 -16
- lockss_turtles-0.6.0.dev22/src/lockss/turtles/plugin.py +278 -0
- lockss_turtles-0.6.0.dev22/src/lockss/turtles/plugin_registry.py +681 -0
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/src/lockss/turtles/plugin_set.py +325 -79
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/src/lockss/turtles/util.py +44 -3
- lockss_turtles-0.6.0.dev20/src/lockss/turtles/plugin.py +0 -134
- lockss_turtles-0.6.0.dev20/src/lockss/turtles/plugin_registry.py +0 -258
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/CHANGELOG.rst +0 -0
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/LICENSE +0 -0
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/src/lockss/turtles/__main__.py +0 -0
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/src/lockss/turtles/app.py +0 -0
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/tests/unittest/lockss/turtles/__init__.py +0 -0
- {lockss_turtles-0.6.0.dev20 → lockss_turtles-0.6.0.dev22}/tests/unittest/lockss/turtles/test_plugin_set.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: lockss-turtles
|
|
3
|
-
Version: 0.6.0.
|
|
3
|
+
Version: 0.6.0.dev22
|
|
4
4
|
Summary: Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Author: Thib Guicherd-Callin
|
|
@@ -34,7 +34,7 @@ Description-Content-Type: text/x-rst
|
|
|
34
34
|
Turtles
|
|
35
35
|
=======
|
|
36
36
|
|
|
37
|
-
.. |RELEASE| replace:: 0.6.0-
|
|
37
|
+
.. |RELEASE| replace:: 0.6.0-dev22
|
|
38
38
|
.. |RELEASE_DATE| replace:: ?
|
|
39
39
|
|
|
40
40
|
.. |HELP| replace:: ``--help/-h``
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "lockss-turtles"
|
|
31
|
-
version = "0.6.0-
|
|
31
|
+
version = "0.6.0-dev22" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
|
|
32
32
|
description = "Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries"
|
|
33
33
|
license = { text = "BSD-3-Clause" }
|
|
34
34
|
readme = "README.rst"
|
|
@@ -5,12 +5,15 @@ Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin
|
|
|
5
5
|
registries.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
#: This package's version.
|
|
9
|
+
__version__ = '0.6.0-dev22'
|
|
9
10
|
|
|
11
|
+
#: This package's copyright.
|
|
10
12
|
__copyright__ = '''
|
|
11
13
|
Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
12
14
|
'''.strip()
|
|
13
15
|
|
|
16
|
+
#: This package's license.
|
|
14
17
|
__license__ = __copyright__ + '\n\n' + '''
|
|
15
18
|
Redistribution and use in source and binary forms, with or without
|
|
16
19
|
modification, are permitted provided that the following conditions are met:
|
|
@@ -144,22 +144,18 @@ class PluginJarOptions(BaseModel):
|
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
class NonInteractiveOptions(BaseModel):
|
|
147
|
-
non_interactive: Optional[bool] = Field(False,
|
|
147
|
+
non_interactive: Optional[bool] = Field(False,
|
|
148
|
+
description='(plugin signing credentials) disallow interactive prompts')
|
|
148
149
|
|
|
149
150
|
|
|
150
|
-
class
|
|
151
|
-
pass
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
class DeployPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginJarOptions):
|
|
155
|
-
pass
|
|
151
|
+
class TurtlesCommand(BaseModel):
|
|
156
152
|
|
|
153
|
+
class BuildPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginBuildingOptions, PluginIdentifierOptions): pass
|
|
157
154
|
|
|
158
|
-
class
|
|
159
|
-
pass
|
|
155
|
+
class DeployPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginJarOptions): pass
|
|
160
156
|
|
|
157
|
+
class ReleasePluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginBuildingOptions, PluginIdentifierOptions): pass
|
|
161
158
|
|
|
162
|
-
class TurtlesCommand(BaseModel):
|
|
163
159
|
bp: Optional[BuildPluginCommand] = Field(description='synonym for: build-plugin')
|
|
164
160
|
build_plugin: Optional[BuildPluginCommand] = Field(description='build plugins', alias='build-plugin')
|
|
165
161
|
copyright: Optional[StringCommand.type(__copyright__)] = Field(description=COPYRIGHT_DESCRIPTION)
|
|
@@ -237,10 +233,10 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
237
233
|
# if len(result) > 0:
|
|
238
234
|
# self._tabulate(title, result, headers)
|
|
239
235
|
|
|
240
|
-
def _bp(self, build_plugin_command: BuildPluginCommand) -> None:
|
|
236
|
+
def _bp(self, build_plugin_command: TurtlesCommand.BuildPluginCommand) -> None:
|
|
241
237
|
return self._build_plugin(build_plugin_command)
|
|
242
238
|
|
|
243
|
-
def _build_plugin(self, build_plugin_command: BuildPluginCommand) -> None:
|
|
239
|
+
def _build_plugin(self, build_plugin_command: TurtlesCommand.BuildPluginCommand) -> None:
|
|
244
240
|
errs = []
|
|
245
241
|
for psc in build_plugin_command.get_plugin_set_catalogs():
|
|
246
242
|
try:
|
|
@@ -276,7 +272,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
276
272
|
def _copyright(self, string_command: StringCommand) -> None:
|
|
277
273
|
self._do_string_command(string_command)
|
|
278
274
|
|
|
279
|
-
def _deploy_plugin(self, deploy_plugin_command: DeployPluginCommand) -> None:
|
|
275
|
+
def _deploy_plugin(self, deploy_plugin_command: TurtlesCommand.DeployPluginCommand) -> None:
|
|
280
276
|
errs = []
|
|
281
277
|
for prc in deploy_plugin_command.get_plugin_registry_catalogs():
|
|
282
278
|
try:
|
|
@@ -307,7 +303,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
307
303
|
def _do_string_command(self, string_command: StringCommand) -> None:
|
|
308
304
|
string_command()
|
|
309
305
|
|
|
310
|
-
def _dp(self, deploy_plugin_command: DeployPluginCommand) -> None:
|
|
306
|
+
def _dp(self, deploy_plugin_command: TurtlesCommand.DeployPluginCommand) -> None:
|
|
311
307
|
return self._deploy_plugin(deploy_plugin_command)
|
|
312
308
|
|
|
313
309
|
def _license(self, string_command: StringCommand) -> None:
|
|
@@ -322,7 +318,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
322
318
|
self._parser.error('no plugin signing password specified while in non-interactive mode')
|
|
323
319
|
self._app.set_password(lambda: _p)
|
|
324
320
|
|
|
325
|
-
def _release_plugin(self, release_plugin_command: ReleasePluginCommand) -> None:
|
|
321
|
+
def _release_plugin(self, release_plugin_command: TurtlesCommand.ReleasePluginCommand) -> None:
|
|
326
322
|
errs = []
|
|
327
323
|
for psc in release_plugin_command.get_plugin_set_catalogs():
|
|
328
324
|
try:
|
|
@@ -371,7 +367,7 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
371
367
|
headers=['Plugin identifier', 'Plugin version', 'Plugin registry', 'Plugin registry layer', 'Deployed JAR'],
|
|
372
368
|
tablefmt=release_plugin_command.output_format))
|
|
373
369
|
|
|
374
|
-
def _rp(self, release_plugin_command: ReleasePluginCommand) -> None:
|
|
370
|
+
def _rp(self, release_plugin_command: TurtlesCommand.ReleasePluginCommand) -> None:
|
|
375
371
|
self._release_plugin(release_plugin_command)
|
|
376
372
|
|
|
377
373
|
def _version(self, string_command: StringCommand) -> None:
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software without
|
|
17
|
+
# specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
Library to represent a LOCKSS plugin.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Remove in Python 3.14
|
|
36
|
+
# See https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class/33533514#33533514
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from collections.abc import Callable
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any, AnyStr, IO, Optional
|
|
42
|
+
import xml.etree.ElementTree as ET
|
|
43
|
+
from zipfile import ZipFile
|
|
44
|
+
|
|
45
|
+
import java_manifest
|
|
46
|
+
from lockss.pybasic.fileutil import path
|
|
47
|
+
|
|
48
|
+
from .util import PathOrStr
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
#: A type alias for plugin identifiers.
|
|
52
|
+
PluginIdentifier = str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Plugin(object):
|
|
56
|
+
"""
|
|
57
|
+
An object to represent a LOCKSS plugin.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, plugin_file: IO[AnyStr], plugin_path: PathOrStr) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Constructor.
|
|
63
|
+
|
|
64
|
+
Other exceptions than ``RuntimeError`` may be raised if the plugin
|
|
65
|
+
definition file cannot be parsed by ``xml.etree.ElementTree``.
|
|
66
|
+
|
|
67
|
+
:param plugin_file: An open file-like object that can read the plugin
|
|
68
|
+
definition file.
|
|
69
|
+
:type plugin_file: IO[AnyStr]
|
|
70
|
+
:param plugin_path: A string (or Path) representing the hierarchical
|
|
71
|
+
path of the plugin definition file (as a real file,
|
|
72
|
+
or as a file entry in a JAR file).
|
|
73
|
+
:type plugin_path: PathOrStr
|
|
74
|
+
:raises RuntimeError: If the plugin definition file parses as XML but
|
|
75
|
+
the top-level element is not <map>.
|
|
76
|
+
"""
|
|
77
|
+
super().__init__()
|
|
78
|
+
self._path = plugin_path
|
|
79
|
+
self._parsed = ET.parse(plugin_file).getroot()
|
|
80
|
+
tag = self._parsed.tag
|
|
81
|
+
if tag != 'map':
|
|
82
|
+
raise RuntimeError(f'{plugin_path!s}: invalid root element: {tag}')
|
|
83
|
+
|
|
84
|
+
def get_aux_packages(self) -> list[str]:
|
|
85
|
+
"""
|
|
86
|
+
Returns the (possibly empty) list of auxiliary code packages declared by
|
|
87
|
+
the plugin (``plugin_aux_packages``).
|
|
88
|
+
|
|
89
|
+
:return: A non-null list of strings representing auxiliary code
|
|
90
|
+
packages.
|
|
91
|
+
:rtype: list[str]
|
|
92
|
+
"""
|
|
93
|
+
key = 'plugin_aux_packages'
|
|
94
|
+
lst = [x[1] for x in self._parsed.findall('entry') if x[0].tag == 'string' and x[0].text == key]
|
|
95
|
+
if lst is None or len(lst) < 1:
|
|
96
|
+
return []
|
|
97
|
+
if len(lst) > 1:
|
|
98
|
+
raise ValueError(f'plugin declares {len(lst)} entries for {key}')
|
|
99
|
+
return [x.text for x in lst[0].findall('string')]
|
|
100
|
+
|
|
101
|
+
def get_identifier(self) -> Optional[PluginIdentifier]:
|
|
102
|
+
"""
|
|
103
|
+
Get this plugin's identifier (``plugin_identifier``).
|
|
104
|
+
|
|
105
|
+
:return: A plugin identifier, or None if missing.
|
|
106
|
+
:rtype: Optional[PluginIdentifier]
|
|
107
|
+
:raises ValueError: If the plugin definition contains more than one.
|
|
108
|
+
"""
|
|
109
|
+
return self._only_one('plugin_identifier')
|
|
110
|
+
|
|
111
|
+
def get_name(self) -> Optional[str]:
|
|
112
|
+
"""
|
|
113
|
+
Get this plugin's name (``plugin_name``).
|
|
114
|
+
|
|
115
|
+
:return: A plugin name, or None if missing.
|
|
116
|
+
:rtype: Optional[str]
|
|
117
|
+
:raises ValueError: If the plugin definition contains more than one.
|
|
118
|
+
"""
|
|
119
|
+
return self._only_one('plugin_name')
|
|
120
|
+
|
|
121
|
+
def get_parent_identifier(self) -> Optional[PluginIdentifier]:
|
|
122
|
+
"""
|
|
123
|
+
Get this plugin's parent identifier (``plugin_parent``).
|
|
124
|
+
|
|
125
|
+
:return: A parent plugin identifier, or None if this plugin has no
|
|
126
|
+
parent.
|
|
127
|
+
:rtype: Optional[PluginIdentifier]
|
|
128
|
+
:raises ValueError: If the plugin definition contains more than one.
|
|
129
|
+
"""
|
|
130
|
+
return self._only_one('plugin_parent')
|
|
131
|
+
|
|
132
|
+
def get_parent_version(self) -> Optional[int]:
|
|
133
|
+
"""
|
|
134
|
+
Get this plugin's parent version (``plugin_parent_version``).
|
|
135
|
+
|
|
136
|
+
:return: A parent plugin version, or None if this plugin has no
|
|
137
|
+
parent.
|
|
138
|
+
:rtype: Optional[int]
|
|
139
|
+
:raises ValueError: If the plugin definition contains more than one.
|
|
140
|
+
"""
|
|
141
|
+
return self._only_one('plugin_parent_version', int)
|
|
142
|
+
|
|
143
|
+
def get_version(self) -> Optional[int]:
|
|
144
|
+
"""
|
|
145
|
+
Get this plugin's version (``plugin_version``).
|
|
146
|
+
|
|
147
|
+
:return: A plugin version, or None if missing.
|
|
148
|
+
:rtype: Optional[int]
|
|
149
|
+
:raises ValueError: If the plugin definition contains more than one.
|
|
150
|
+
"""
|
|
151
|
+
return self._only_one('plugin_version', int)
|
|
152
|
+
|
|
153
|
+
def _only_one(self, key: str, result: Callable[[str], Any]=str) -> Optional[Any]:
|
|
154
|
+
"""
|
|
155
|
+
Retrieves the value of a given key in the plugin definition, optionally
|
|
156
|
+
coerced into a representation (by default simply a string).
|
|
157
|
+
|
|
158
|
+
:param key: A plugin key.
|
|
159
|
+
:param key: str
|
|
160
|
+
:param result: A functor that takes a string and returns the desired
|
|
161
|
+
representation; by default this is the string constructor
|
|
162
|
+
``str``, meaning by default the string values are
|
|
163
|
+
returned unchanged.
|
|
164
|
+
:param result: Callable[[str], Any]
|
|
165
|
+
:return: The value for the given key, coerced through the given functor,
|
|
166
|
+
or None if the plugin definition does not contain any entry
|
|
167
|
+
with the given key.
|
|
168
|
+
:rtype: Optional[Any]
|
|
169
|
+
:raises ValueError: If the plugin definition contains more than one
|
|
170
|
+
entry with the given key.
|
|
171
|
+
"""
|
|
172
|
+
lst = [x[1].text for x in self._parsed.findall('entry') if x[0].tag == 'string' and x[0].text == key]
|
|
173
|
+
if lst is None or len(lst) < 1:
|
|
174
|
+
return None
|
|
175
|
+
if len(lst) > 1:
|
|
176
|
+
raise ValueError(f'plugin declares {len(lst)} entries for {key}')
|
|
177
|
+
return result(lst[0])
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def from_jar(jar_path_or_str: PathOrStr) -> Plugin:
|
|
181
|
+
"""
|
|
182
|
+
Instantiates a Plugin object from the given plugin JAR file.
|
|
183
|
+
|
|
184
|
+
:param jar_path_or_str: The path to a plugin JAR.
|
|
185
|
+
:type jar_path_or_str: PathOrStr
|
|
186
|
+
:return: A Plugin object.
|
|
187
|
+
:rtype: Plugin
|
|
188
|
+
"""
|
|
189
|
+
jar_path = path(jar_path_or_str)
|
|
190
|
+
plugin_id = Plugin.id_from_jar(jar_path)
|
|
191
|
+
plugin_fstr = str(Plugin.id_to_file(plugin_id))
|
|
192
|
+
with ZipFile(jar_path, 'r') as zip_file:
|
|
193
|
+
with zip_file.open(plugin_fstr, 'r') as plugin_file:
|
|
194
|
+
return Plugin(plugin_file, plugin_fstr)
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def from_path(path_or_str: PathOrStr) -> Plugin:
|
|
198
|
+
"""
|
|
199
|
+
Instantiates a Plugin object from the given plugin file.
|
|
200
|
+
|
|
201
|
+
:param path_or_str: The path to a plugin file.
|
|
202
|
+
:type path_or_str: PathOrStr
|
|
203
|
+
:return: A Plugin object.
|
|
204
|
+
:rtype: Plugin
|
|
205
|
+
"""
|
|
206
|
+
fpath = path(path_or_str)
|
|
207
|
+
with fpath.open('r') as input_file:
|
|
208
|
+
return Plugin(input_file, fpath)
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def file_to_id(plugin_fstr: str) -> PluginIdentifier:
|
|
212
|
+
"""
|
|
213
|
+
Converts a plugin file path (ending in ``.xml``) to the implied plugin
|
|
214
|
+
identifier (e.g. ``org/myproject/plugin/MyPlugin.xml`` implies
|
|
215
|
+
``org.myproject.plugin.MyPlugin``).
|
|
216
|
+
|
|
217
|
+
See also ``id_to_file``.
|
|
218
|
+
|
|
219
|
+
:param plugin_fstr: A string file path.
|
|
220
|
+
:type plugin_fstr: str
|
|
221
|
+
:return: A plugin identifier.
|
|
222
|
+
:rtype: PluginIdentifier
|
|
223
|
+
"""
|
|
224
|
+
return plugin_fstr.replace('/', '.')[:-4] # 4 is len('.xml')
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def id_from_jar(jar_path_or_str: PathOrStr) -> PluginIdentifier:
|
|
228
|
+
"""
|
|
229
|
+
Extracts the plugin identifier from a plugin JAR's manifest file.
|
|
230
|
+
|
|
231
|
+
:param jar_path_or_str: The path to a plugin JAR.
|
|
232
|
+
:type jar_path_or_str: PathOrStr
|
|
233
|
+
:return: The plugin identifier extracted from the given plugin JAR's
|
|
234
|
+
manifest file.
|
|
235
|
+
:rtype: PluginIdentifier
|
|
236
|
+
:raises Exception: If the JAR's manifest file has no entry with
|
|
237
|
+
``Lockss-Plugin`` equal to ``true`` and ``Name``
|
|
238
|
+
equal to the packaged plugin's identifier.
|
|
239
|
+
"""
|
|
240
|
+
jar_path = path(jar_path_or_str)
|
|
241
|
+
manifest = java_manifest.from_jar(jar_path)
|
|
242
|
+
for entry in manifest:
|
|
243
|
+
if entry.get('Lockss-Plugin') == 'true':
|
|
244
|
+
name = entry.get('Name')
|
|
245
|
+
if name is None:
|
|
246
|
+
raise Exception(f'{jar_path!s}: Lockss-Plugin entry in META-INF/MANIFEST.MF has no Name value')
|
|
247
|
+
return Plugin.file_to_id(name)
|
|
248
|
+
else:
|
|
249
|
+
raise Exception(f'{jar_path!s}: no Lockss-Plugin entry in META-INF/MANIFEST.MF')
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def id_to_dir(plugin_id: PluginIdentifier) -> Path:
|
|
253
|
+
"""
|
|
254
|
+
Returns the path of the directory containing the given plugin identifier
|
|
255
|
+
(for example ``org/myproject/plugin`` for
|
|
256
|
+
``org.myproject.plugin.MyPlugin``).
|
|
257
|
+
|
|
258
|
+
:param plugin_id: A plugin identifier.
|
|
259
|
+
:type plugin_id: PluginIdentifier
|
|
260
|
+
:return: The directory path containing the given plugin identifier.
|
|
261
|
+
:rtype: Path
|
|
262
|
+
"""
|
|
263
|
+
return Plugin.id_to_file(plugin_id).parent
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
def id_to_file(plugin_id: PluginIdentifier) -> Path:
|
|
267
|
+
"""
|
|
268
|
+
Returns the path of the definition file corresponding to the given
|
|
269
|
+
plugin identifier (for example ``org/myproject/plugin/MyPlugin.xml`` for
|
|
270
|
+
``org.myproject.plugin.MyPlugin``).
|
|
271
|
+
|
|
272
|
+
:param plugin_id: A plugin identifier.
|
|
273
|
+
:type plugin_id: PluginIdentifier
|
|
274
|
+
:return: The path of the definition file corresponding to the given
|
|
275
|
+
plugin identifier.
|
|
276
|
+
:rtype: Path
|
|
277
|
+
"""
|
|
278
|
+
return Path(f'{plugin_id.replace(".", "/")}.xml')
|