multitool 0.8.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multitool-0.8.0 → multitool-0.9.0}/PKG-INFO +45 -46
- {multitool-0.8.0 → multitool-0.9.0}/README.rst +44 -45
- {multitool-0.8.0 → multitool-0.9.0}/multitool/__init__.py +1 -1
- {multitool-0.8.0 → multitool-0.9.0}/multitool/__main__.py +4 -8
- multitool-0.9.0/multitool/bootstrap/commands.py +57 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/console.py +1 -1
- {multitool-0.8.0 → multitool-0.9.0}/multitool/plugins/commands.py +140 -58
- multitool-0.9.0/multitool/run/commands.py +9 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/PKG-INFO +45 -46
- {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/SOURCES.txt +2 -0
- {multitool-0.8.0 → multitool-0.9.0}/pyproject.toml +1 -1
- {multitool-0.8.0 → multitool-0.9.0}/LICENSE +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/cls.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/exceptions.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/plugins/__init__.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/silent.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/utils.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/utils_init.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool/verbose.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/dependency_links.txt +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/entry_points.txt +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/requires.txt +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/top_level.txt +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/setup.cfg +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/tests/__init__.py +0 -0
- {multitool-0.8.0 → multitool-0.9.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multitool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Create and run plugin-based command-line tools.
|
|
5
5
|
Author-email: Matthew Delotavo <matthew.t.delotavo@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,8 +73,9 @@ Usage
|
|
|
73
73
|
-h, --help Show this message and exit.
|
|
74
74
|
|
|
75
75
|
Commands:
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
bootstrap Create the directory structure for a new Python package...
|
|
77
|
+
plugins Manage plugin repositories.
|
|
78
|
+
run Run installed plugin commands.
|
|
78
79
|
|
|
79
80
|
----------------
|
|
80
81
|
Managing plugins
|
|
@@ -145,31 +146,45 @@ plugins directory to use it locally.
|
|
|
145
146
|
Configuring
|
|
146
147
|
^^^^^^^^^^^
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
Add a plugin repository with::
|
|
149
150
|
|
|
150
|
-
multitool plugins
|
|
151
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git
|
|
152
|
+
|
|
153
|
+
By default, the repository name is used as the source key. To specify your own
|
|
154
|
+
key::
|
|
155
|
+
|
|
156
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git --key example
|
|
157
|
+
|
|
158
|
+
Remove a configured plugin source by key or URL::
|
|
151
159
|
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
multitool plugins remove multitool-plugins
|
|
161
|
+
|
|
162
|
+
multitool plugins remove https://github.com/mdelotavo/multitool-plugins.git
|
|
163
|
+
|
|
164
|
+
Apply configuration changes (install new plugins and update existing plugins)
|
|
165
|
+
with::
|
|
154
166
|
|
|
155
167
|
multitool plugins update
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
For bulk edits, you can edit the configuration file directly::
|
|
170
|
+
|
|
171
|
+
multitool plugins configure
|
|
172
|
+
|
|
173
|
+
To automatically apply configuration changes after saving and closing the file
|
|
174
|
+
(install new plugins, update existing plugins, and prune removed plugins), use::
|
|
160
175
|
|
|
161
176
|
multitool plugins configure -a
|
|
162
177
|
|
|
163
178
|
Example configuration::
|
|
164
179
|
|
|
165
180
|
[sources]
|
|
166
|
-
|
|
181
|
+
multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
|
|
167
182
|
|
|
168
|
-
|
|
183
|
+
Configured repositories are cloned into::
|
|
169
184
|
|
|
170
185
|
~/.multitool/plugins/
|
|
171
186
|
|
|
172
|
-
|
|
187
|
+
Multiple repositories may be configured as long as each source key is unique.
|
|
173
188
|
|
|
174
189
|
^^^^^^^^^^
|
|
175
190
|
Installing
|
|
@@ -177,14 +192,14 @@ Installing
|
|
|
177
192
|
|
|
178
193
|
Install the example plugins::
|
|
179
194
|
|
|
180
|
-
|
|
195
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git
|
|
181
196
|
|
|
182
197
|
multitool plugins update
|
|
183
|
-
|
|
198
|
+
|
|
184
199
|
multitool plugins show
|
|
185
|
-
multitool plugins show -n
|
|
186
|
-
multitool plugins show -n
|
|
187
|
-
multitool plugins show -n
|
|
200
|
+
multitool plugins show -n multitool-plugins
|
|
201
|
+
multitool plugins show -n multitool-plugins --show-commit-only
|
|
202
|
+
multitool plugins show -n multitool-plugins --show-dependencies-only
|
|
188
203
|
|
|
189
204
|
multitool run examples -h
|
|
190
205
|
|
|
@@ -219,7 +234,7 @@ the remaining packages manually. For example::
|
|
|
219
234
|
|
|
220
235
|
Updating plugins
|
|
221
236
|
----------------
|
|
222
|
-
Updating
|
|
237
|
+
Updating multitool-plugins... Done
|
|
223
238
|
Updating test... Skipped Git pull (not a Git repository)
|
|
224
239
|
|
|
225
240
|
Checking Python package dependencies
|
|
@@ -247,10 +262,20 @@ You can also display a plugin's declared dependencies at any time::
|
|
|
247
262
|
Pruning
|
|
248
263
|
^^^^^^^
|
|
249
264
|
|
|
250
|
-
|
|
265
|
+
To uninstall a plugin repository, first remove it from the configured sources::
|
|
266
|
+
|
|
267
|
+
multitool plugins remove multitool-plugins
|
|
268
|
+
|
|
269
|
+
Then remove any local repositories that are no longer configured::
|
|
251
270
|
|
|
252
271
|
multitool plugins prune
|
|
253
272
|
|
|
273
|
+
Alternatively, running::
|
|
274
|
+
|
|
275
|
+
multitool plugins configure -a
|
|
276
|
+
|
|
277
|
+
automatically prunes repositories removed from the configuration.
|
|
278
|
+
|
|
254
279
|
^^^^^^^
|
|
255
280
|
Showing
|
|
256
281
|
^^^^^^^
|
|
@@ -274,32 +299,6 @@ If a plugin fails to install or load, check the log file::
|
|
|
274
299
|
|
|
275
300
|
It contains installation, dependency, and Git-related errors.
|
|
276
301
|
|
|
277
|
-
-----------
|
|
278
|
-
Limitations
|
|
279
|
-
-----------
|
|
280
|
-
|
|
281
|
-
Plugin command names must be unique across all installed repositories.
|
|
282
|
-
|
|
283
|
-
To avoid naming conflicts, plugin modules should follow the convention of
|
|
284
|
-
including the repository owner and repository name in the command name.
|
|
285
|
-
|
|
286
|
-
For example, a repository configured as::
|
|
287
|
-
|
|
288
|
-
[sources]
|
|
289
|
-
mdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
|
|
290
|
-
|
|
291
|
-
should expose commands using a unique name such as::
|
|
292
|
-
|
|
293
|
-
mdelotavo-multitool-plugins
|
|
294
|
-
|
|
295
|
-
This reduces the likelihood of collisions when multiple repositories expose
|
|
296
|
-
plugins with the same command name.
|
|
297
|
-
|
|
298
|
-
If duplicate command names are detected, Multitool will display an error
|
|
299
|
-
prompting you to delete the detected conflicting plugin repositories and, if
|
|
300
|
-
applicable, remove the corresponding remote sources from your plugin
|
|
301
|
-
configuration.
|
|
302
|
-
|
|
303
302
|
|
|
304
303
|
.. _`click`: https://click.palletsprojects.com/
|
|
305
304
|
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
@@ -49,8 +49,9 @@ Usage
|
|
|
49
49
|
-h, --help Show this message and exit.
|
|
50
50
|
|
|
51
51
|
Commands:
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
bootstrap Create the directory structure for a new Python package...
|
|
53
|
+
plugins Manage plugin repositories.
|
|
54
|
+
run Run installed plugin commands.
|
|
54
55
|
|
|
55
56
|
----------------
|
|
56
57
|
Managing plugins
|
|
@@ -121,31 +122,45 @@ plugins directory to use it locally.
|
|
|
121
122
|
Configuring
|
|
122
123
|
^^^^^^^^^^^
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
Add a plugin repository with::
|
|
125
126
|
|
|
126
|
-
multitool plugins
|
|
127
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git
|
|
128
|
+
|
|
129
|
+
By default, the repository name is used as the source key. To specify your own
|
|
130
|
+
key::
|
|
131
|
+
|
|
132
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git --key example
|
|
133
|
+
|
|
134
|
+
Remove a configured plugin source by key or URL::
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
multitool plugins remove multitool-plugins
|
|
137
|
+
|
|
138
|
+
multitool plugins remove https://github.com/mdelotavo/multitool-plugins.git
|
|
139
|
+
|
|
140
|
+
Apply configuration changes (install new plugins and update existing plugins)
|
|
141
|
+
with::
|
|
130
142
|
|
|
131
143
|
multitool plugins update
|
|
132
144
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
For bulk edits, you can edit the configuration file directly::
|
|
146
|
+
|
|
147
|
+
multitool plugins configure
|
|
148
|
+
|
|
149
|
+
To automatically apply configuration changes after saving and closing the file
|
|
150
|
+
(install new plugins, update existing plugins, and prune removed plugins), use::
|
|
136
151
|
|
|
137
152
|
multitool plugins configure -a
|
|
138
153
|
|
|
139
154
|
Example configuration::
|
|
140
155
|
|
|
141
156
|
[sources]
|
|
142
|
-
|
|
157
|
+
multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
|
|
143
158
|
|
|
144
|
-
|
|
159
|
+
Configured repositories are cloned into::
|
|
145
160
|
|
|
146
161
|
~/.multitool/plugins/
|
|
147
162
|
|
|
148
|
-
|
|
163
|
+
Multiple repositories may be configured as long as each source key is unique.
|
|
149
164
|
|
|
150
165
|
^^^^^^^^^^
|
|
151
166
|
Installing
|
|
@@ -153,14 +168,14 @@ Installing
|
|
|
153
168
|
|
|
154
169
|
Install the example plugins::
|
|
155
170
|
|
|
156
|
-
|
|
171
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git
|
|
157
172
|
|
|
158
173
|
multitool plugins update
|
|
159
|
-
|
|
174
|
+
|
|
160
175
|
multitool plugins show
|
|
161
|
-
multitool plugins show -n
|
|
162
|
-
multitool plugins show -n
|
|
163
|
-
multitool plugins show -n
|
|
176
|
+
multitool plugins show -n multitool-plugins
|
|
177
|
+
multitool plugins show -n multitool-plugins --show-commit-only
|
|
178
|
+
multitool plugins show -n multitool-plugins --show-dependencies-only
|
|
164
179
|
|
|
165
180
|
multitool run examples -h
|
|
166
181
|
|
|
@@ -195,7 +210,7 @@ the remaining packages manually. For example::
|
|
|
195
210
|
|
|
196
211
|
Updating plugins
|
|
197
212
|
----------------
|
|
198
|
-
Updating
|
|
213
|
+
Updating multitool-plugins... Done
|
|
199
214
|
Updating test... Skipped Git pull (not a Git repository)
|
|
200
215
|
|
|
201
216
|
Checking Python package dependencies
|
|
@@ -223,10 +238,20 @@ You can also display a plugin's declared dependencies at any time::
|
|
|
223
238
|
Pruning
|
|
224
239
|
^^^^^^^
|
|
225
240
|
|
|
226
|
-
|
|
241
|
+
To uninstall a plugin repository, first remove it from the configured sources::
|
|
242
|
+
|
|
243
|
+
multitool plugins remove multitool-plugins
|
|
244
|
+
|
|
245
|
+
Then remove any local repositories that are no longer configured::
|
|
227
246
|
|
|
228
247
|
multitool plugins prune
|
|
229
248
|
|
|
249
|
+
Alternatively, running::
|
|
250
|
+
|
|
251
|
+
multitool plugins configure -a
|
|
252
|
+
|
|
253
|
+
automatically prunes repositories removed from the configuration.
|
|
254
|
+
|
|
230
255
|
^^^^^^^
|
|
231
256
|
Showing
|
|
232
257
|
^^^^^^^
|
|
@@ -250,32 +275,6 @@ If a plugin fails to install or load, check the log file::
|
|
|
250
275
|
|
|
251
276
|
It contains installation, dependency, and Git-related errors.
|
|
252
277
|
|
|
253
|
-
-----------
|
|
254
|
-
Limitations
|
|
255
|
-
-----------
|
|
256
|
-
|
|
257
|
-
Plugin command names must be unique across all installed repositories.
|
|
258
|
-
|
|
259
|
-
To avoid naming conflicts, plugin modules should follow the convention of
|
|
260
|
-
including the repository owner and repository name in the command name.
|
|
261
|
-
|
|
262
|
-
For example, a repository configured as::
|
|
263
|
-
|
|
264
|
-
[sources]
|
|
265
|
-
mdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
|
|
266
|
-
|
|
267
|
-
should expose commands using a unique name such as::
|
|
268
|
-
|
|
269
|
-
mdelotavo-multitool-plugins
|
|
270
|
-
|
|
271
|
-
This reduces the likelihood of collisions when multiple repositories expose
|
|
272
|
-
plugins with the same command name.
|
|
273
|
-
|
|
274
|
-
If duplicate command names are detected, Multitool will display an error
|
|
275
|
-
prompting you to delete the detected conflicting plugin repositories and, if
|
|
276
|
-
applicable, remove the corresponding remote sources from your plugin
|
|
277
|
-
configuration.
|
|
278
|
-
|
|
279
278
|
|
|
280
279
|
.. _`click`: https://click.palletsprojects.com/
|
|
281
280
|
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
APP = "multitool"
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.9.0"
|
|
3
3
|
description = "Create and run plugin-based command-line tools."
|
|
4
4
|
long_description = ("Create and run plugin-based command-line tools by dynamically loading Click commands from local or Git-managed plugin repositories.")
|
|
5
5
|
|
|
@@ -5,10 +5,12 @@ import click
|
|
|
5
5
|
|
|
6
6
|
from multitool import APP, MULTITOOL_LOG_FILE, MULTITOOL_PLUGINS_DIRECTORY
|
|
7
7
|
from multitool import __version__ as version
|
|
8
|
+
from multitool.bootstrap.commands import bootstrap
|
|
8
9
|
from multitool.cls import AliasedGroup
|
|
9
10
|
from multitool.exceptions import wrap_with_exception_handling
|
|
10
11
|
from multitool.plugins.commands import plugins
|
|
11
|
-
from multitool.
|
|
12
|
+
from multitool.run.commands import run
|
|
13
|
+
from multitool.utils import configure_root_logger, for_each_file, load_plugins
|
|
12
14
|
|
|
13
15
|
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
14
16
|
|
|
@@ -21,17 +23,11 @@ def cli(ctx):
|
|
|
21
23
|
ctx.ensure_object(dict)
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
@click.group(cls=AliasedGroup)
|
|
25
|
-
def run():
|
|
26
|
-
"""Run installed plugin commands."""
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
26
|
@wrap_with_exception_handling
|
|
31
27
|
def main():
|
|
32
28
|
configure_root_logger(MULTITOOL_LOG_FILE)
|
|
33
29
|
|
|
34
|
-
cli_commands = {plugins, run}
|
|
30
|
+
cli_commands = {plugins, run, bootstrap}
|
|
35
31
|
|
|
36
32
|
run_commands = {}
|
|
37
33
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from multitool import console
|
|
6
|
+
from multitool.silent import common_silent_options
|
|
7
|
+
from multitool.utils import mkdir, touch
|
|
8
|
+
from multitool.verbose import common_verbose_options
|
|
9
|
+
|
|
10
|
+
PROJECT_STRUCTURE = {
|
|
11
|
+
"pyproject.toml": None,
|
|
12
|
+
"{app}": {
|
|
13
|
+
"__init__.py": None,
|
|
14
|
+
"__main__.py": None,
|
|
15
|
+
# "cls.py": None,
|
|
16
|
+
"console.py": None,
|
|
17
|
+
"exceptions.py": None,
|
|
18
|
+
"silent.py": None,
|
|
19
|
+
"utils.py": None,
|
|
20
|
+
"utils_init.py": None,
|
|
21
|
+
"verbose.py": None,
|
|
22
|
+
"submodule": {
|
|
23
|
+
"__init__.py": None,
|
|
24
|
+
"commands.py": None,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_structure(root: Path, structure: dict, app: str):
|
|
31
|
+
for name, children in structure.items():
|
|
32
|
+
path = root / name.format(app=app)
|
|
33
|
+
|
|
34
|
+
if children is None:
|
|
35
|
+
touch(path)
|
|
36
|
+
else:
|
|
37
|
+
mkdir(path)
|
|
38
|
+
create_structure(path, children, app)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@click.command(help="Create the directory structure for a new Python package project.")
|
|
42
|
+
@click.argument("name")
|
|
43
|
+
@click.argument(
|
|
44
|
+
"directory",
|
|
45
|
+
type=click.Path(path_type=Path),
|
|
46
|
+
default=".",
|
|
47
|
+
required=False,
|
|
48
|
+
)
|
|
49
|
+
@common_silent_options
|
|
50
|
+
@common_verbose_options
|
|
51
|
+
def bootstrap(silent, verbose, name, directory):
|
|
52
|
+
root = directory / name
|
|
53
|
+
mkdir(root)
|
|
54
|
+
|
|
55
|
+
create_structure(root, PROJECT_STRUCTURE, name)
|
|
56
|
+
|
|
57
|
+
console.echo(f"Created project: {root}")
|
|
@@ -2,33 +2,20 @@ import configparser
|
|
|
2
2
|
import os
|
|
3
3
|
import shutil
|
|
4
4
|
import stat
|
|
5
|
+
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
import uuid
|
|
7
|
-
import shutil
|
|
8
|
-
import subprocess
|
|
9
8
|
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
-
from packaging.requirements import Requirement
|
|
11
9
|
from pathlib import Path
|
|
10
|
+
from urllib.parse import urlparse
|
|
12
11
|
|
|
13
12
|
import click
|
|
14
13
|
from click_option_group import MutuallyExclusiveOptionGroup, optgroup
|
|
14
|
+
from packaging.requirements import Requirement
|
|
15
15
|
|
|
16
|
-
from multitool import (
|
|
17
|
-
APP,
|
|
18
|
-
MULTITOOL_PLUGINS_CONFIG_FILE,
|
|
19
|
-
MULTITOOL_PLUGINS_DIRECTORY,
|
|
20
|
-
MULTITOOL_PLUGINS_INIT_FILE,
|
|
21
|
-
console,
|
|
22
|
-
)
|
|
16
|
+
from multitool import (APP, MULTITOOL_PLUGINS_CONFIG_FILE, MULTITOOL_PLUGINS_DIRECTORY, MULTITOOL_PLUGINS_INIT_FILE, console)
|
|
23
17
|
from multitool.silent import common_silent_options
|
|
24
|
-
from multitool.utils import (
|
|
25
|
-
mkdir,
|
|
26
|
-
touch,
|
|
27
|
-
for_each_file,
|
|
28
|
-
is_dir,
|
|
29
|
-
is_file,
|
|
30
|
-
read_file,
|
|
31
|
-
)
|
|
18
|
+
from multitool.utils import (for_each_file, is_dir, is_file, mkdir, read_file, touch)
|
|
32
19
|
from multitool.verbose import common_verbose_options
|
|
33
20
|
|
|
34
21
|
try:
|
|
@@ -52,15 +39,43 @@ def init():
|
|
|
52
39
|
touch(MULTITOOL_PLUGINS_CONFIG_FILE)
|
|
53
40
|
|
|
54
41
|
|
|
55
|
-
def
|
|
42
|
+
def read_config(section="sources"):
|
|
56
43
|
cfg = configparser.ConfigParser(allow_no_value=True)
|
|
57
44
|
cfg.read(MULTITOOL_PLUGINS_CONFIG_FILE)
|
|
58
45
|
return dict(cfg._sections.get(section, {}))
|
|
59
46
|
|
|
60
47
|
|
|
48
|
+
def write_config(data, section="sources"):
|
|
49
|
+
cfg = configparser.ConfigParser(allow_no_value=True)
|
|
50
|
+
cfg[section] = data
|
|
51
|
+
|
|
52
|
+
with open(MULTITOOL_PLUGINS_CONFIG_FILE, "w") as f:
|
|
53
|
+
cfg.write(f)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def normalize_git_url(url):
|
|
57
|
+
return url.removesuffix(".git").rstrip("/")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def source_key_from_url(url):
|
|
61
|
+
"""
|
|
62
|
+
https://github.com/user/my-plugin.git -> my-plugin
|
|
63
|
+
git@github.com:user/my-plugin.git -> my-plugin
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
url = normalize_git_url(url)
|
|
67
|
+
|
|
68
|
+
if url.startswith("git@"):
|
|
69
|
+
path = url.split(":", 1)[1]
|
|
70
|
+
else:
|
|
71
|
+
path = urlparse(url).path
|
|
72
|
+
|
|
73
|
+
return Path(path).name
|
|
74
|
+
|
|
75
|
+
|
|
61
76
|
def clone():
|
|
62
77
|
init()
|
|
63
|
-
for name, uri in
|
|
78
|
+
for name, uri in read_config().items():
|
|
64
79
|
dest = Path(MULTITOOL_PLUGINS_DIRECTORY) / name
|
|
65
80
|
if is_dir(dest):
|
|
66
81
|
continue
|
|
@@ -254,7 +269,7 @@ def _chmod(func, p, _):
|
|
|
254
269
|
|
|
255
270
|
|
|
256
271
|
def prune_repos():
|
|
257
|
-
sources =
|
|
272
|
+
sources = read_config()
|
|
258
273
|
|
|
259
274
|
def fn(p):
|
|
260
275
|
if not is_dir(p):
|
|
@@ -306,7 +321,7 @@ def plugins():
|
|
|
306
321
|
pass
|
|
307
322
|
|
|
308
323
|
|
|
309
|
-
@plugins.command()
|
|
324
|
+
@plugins.command(help="Edit the plugin source configuration.")
|
|
310
325
|
@common_silent_options
|
|
311
326
|
@common_verbose_options
|
|
312
327
|
@click.option("-a/-A", "--apply-changes/--no-apply-changes", default=False)
|
|
@@ -323,7 +338,70 @@ def configure(silent, verbose, apply_changes):
|
|
|
323
338
|
console.echo(f"\nRun `{APP} plugins update` to apply changes.\n")
|
|
324
339
|
|
|
325
340
|
|
|
326
|
-
@plugins.command()
|
|
341
|
+
@plugins.command(help="Add a plugin source.")
|
|
342
|
+
@common_silent_options
|
|
343
|
+
@common_verbose_options
|
|
344
|
+
@click.argument("url")
|
|
345
|
+
@click.option(
|
|
346
|
+
"-k",
|
|
347
|
+
"--key",
|
|
348
|
+
help="Plugin source name. Defaults to the repository name.",
|
|
349
|
+
)
|
|
350
|
+
def add(silent, verbose, url, key):
|
|
351
|
+
init()
|
|
352
|
+
|
|
353
|
+
sources = read_config()
|
|
354
|
+
|
|
355
|
+
key = key or source_key_from_url(url)
|
|
356
|
+
|
|
357
|
+
normalized = normalize_git_url(url)
|
|
358
|
+
|
|
359
|
+
for existing_key, existing_url in sources.items():
|
|
360
|
+
if normalize_git_url(existing_url) == normalized:
|
|
361
|
+
console.echo(f'Source "{existing_key}" already uses "{existing_url}".')
|
|
362
|
+
sys.exit(1)
|
|
363
|
+
|
|
364
|
+
if key in sources:
|
|
365
|
+
console.echo(f'Source "{key}" already exists.')
|
|
366
|
+
sys.exit(1)
|
|
367
|
+
|
|
368
|
+
sources[key] = url
|
|
369
|
+
write_config(sources)
|
|
370
|
+
|
|
371
|
+
console.echo(f'Added "{key}" -> {url}')
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@plugins.command(help="Remove a plugin source.")
|
|
375
|
+
@common_silent_options
|
|
376
|
+
@common_verbose_options
|
|
377
|
+
@click.argument("source")
|
|
378
|
+
def remove(silent, verbose, source):
|
|
379
|
+
init()
|
|
380
|
+
|
|
381
|
+
sources = read_config()
|
|
382
|
+
|
|
383
|
+
if source in sources:
|
|
384
|
+
del sources[source]
|
|
385
|
+
write_config(sources)
|
|
386
|
+
|
|
387
|
+
console.echo(f'Removed "{source}".')
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
normalized = normalize_git_url(source)
|
|
391
|
+
|
|
392
|
+
for key, url in list(sources.items()):
|
|
393
|
+
if normalize_git_url(url) == normalized:
|
|
394
|
+
del sources[key]
|
|
395
|
+
write_config(sources)
|
|
396
|
+
|
|
397
|
+
console.echo(f'Removed "{key}".')
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
console.echo(f'Plugin source "{source}" not found.')
|
|
401
|
+
sys.exit(1)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@plugins.command(help="Install, update, and synchronize plugins with the configured sources.")
|
|
327
405
|
@common_silent_options
|
|
328
406
|
@common_verbose_options
|
|
329
407
|
@click.option("-n", "--name")
|
|
@@ -339,7 +417,7 @@ def update(silent, verbose, name):
|
|
|
339
417
|
install_plugin_dependencies(name)
|
|
340
418
|
|
|
341
419
|
|
|
342
|
-
@plugins.command()
|
|
420
|
+
@plugins.command(help="Show configured plugins or plugin information.")
|
|
343
421
|
@common_silent_options
|
|
344
422
|
@common_verbose_options
|
|
345
423
|
@click.option("-n", "--name")
|
|
@@ -347,7 +425,7 @@ def update(silent, verbose, name):
|
|
|
347
425
|
@optgroup.option("--show-commit-only/--no-show-commit-only", default=False)
|
|
348
426
|
@optgroup.option("--show-dependencies-only/--no-show-dependencies-only", default=False)
|
|
349
427
|
def show(silent, verbose, name, show_commit_only, show_dependencies_only):
|
|
350
|
-
sources =
|
|
428
|
+
sources = read_config()
|
|
351
429
|
|
|
352
430
|
if not name:
|
|
353
431
|
for k, v in sources.items():
|
|
@@ -400,7 +478,7 @@ def show(silent, verbose, name, show_commit_only, show_dependencies_only):
|
|
|
400
478
|
console.echo(f"{k}: {v}")
|
|
401
479
|
|
|
402
480
|
|
|
403
|
-
@plugins.command()
|
|
481
|
+
@plugins.command(help="Remove unmanaged plugin repositories.")
|
|
404
482
|
@common_silent_options
|
|
405
483
|
@common_verbose_options
|
|
406
484
|
def prune(silent, verbose):
|
|
@@ -408,7 +486,7 @@ def prune(silent, verbose):
|
|
|
408
486
|
prune_repos()
|
|
409
487
|
|
|
410
488
|
|
|
411
|
-
@plugins.command()
|
|
489
|
+
@plugins.command(help="Create a new plugin project.")
|
|
412
490
|
@common_silent_options
|
|
413
491
|
@common_verbose_options
|
|
414
492
|
@click.argument("name")
|
|
@@ -567,34 +645,38 @@ def hello(
|
|
|
567
645
|
touch(root / "README.md")
|
|
568
646
|
touch(root / "LICENSE")
|
|
569
647
|
|
|
570
|
-
console.echo(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
648
|
+
console.echo(
|
|
649
|
+
f"""\
|
|
650
|
+
Created plugin "{name}" at:
|
|
651
|
+
{root}
|
|
652
|
+
|
|
653
|
+
Generated module:
|
|
654
|
+
{module}.py
|
|
655
|
+
|
|
656
|
+
Next steps:
|
|
657
|
+
• Add commands to:
|
|
658
|
+
{module}.py
|
|
659
|
+
|
|
660
|
+
• To use this plugin locally:
|
|
661
|
+
Copy the plugin directory into another {APP}
|
|
662
|
+
plugins directory:
|
|
663
|
+
~/.{APP}/plugins/
|
|
664
|
+
|
|
665
|
+
• To distribute this plugin:
|
|
666
|
+
1. Initialize a Git repository.
|
|
667
|
+
2. Commit and push it to a remote source
|
|
668
|
+
(GitHub, GitLab, or another Git server).
|
|
669
|
+
3. Add the repository URL to your plugin config:
|
|
670
|
+
{APP} plugins configure
|
|
671
|
+
|
|
672
|
+
Example:
|
|
673
|
+
|
|
674
|
+
[sources]
|
|
675
|
+
{name} = https://github.com/<user>/{name}.git
|
|
676
|
+
|
|
677
|
+
HTTPS and SSH Git URLs are supported.
|
|
678
|
+
|
|
679
|
+
4. Install or update plugins:
|
|
680
|
+
{APP} plugins update
|
|
681
|
+
"""
|
|
682
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multitool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Create and run plugin-based command-line tools.
|
|
5
5
|
Author-email: Matthew Delotavo <matthew.t.delotavo@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,8 +73,9 @@ Usage
|
|
|
73
73
|
-h, --help Show this message and exit.
|
|
74
74
|
|
|
75
75
|
Commands:
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
bootstrap Create the directory structure for a new Python package...
|
|
77
|
+
plugins Manage plugin repositories.
|
|
78
|
+
run Run installed plugin commands.
|
|
78
79
|
|
|
79
80
|
----------------
|
|
80
81
|
Managing plugins
|
|
@@ -145,31 +146,45 @@ plugins directory to use it locally.
|
|
|
145
146
|
Configuring
|
|
146
147
|
^^^^^^^^^^^
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
Add a plugin repository with::
|
|
149
150
|
|
|
150
|
-
multitool plugins
|
|
151
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git
|
|
152
|
+
|
|
153
|
+
By default, the repository name is used as the source key. To specify your own
|
|
154
|
+
key::
|
|
155
|
+
|
|
156
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git --key example
|
|
157
|
+
|
|
158
|
+
Remove a configured plugin source by key or URL::
|
|
151
159
|
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
multitool plugins remove multitool-plugins
|
|
161
|
+
|
|
162
|
+
multitool plugins remove https://github.com/mdelotavo/multitool-plugins.git
|
|
163
|
+
|
|
164
|
+
Apply configuration changes (install new plugins and update existing plugins)
|
|
165
|
+
with::
|
|
154
166
|
|
|
155
167
|
multitool plugins update
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
For bulk edits, you can edit the configuration file directly::
|
|
170
|
+
|
|
171
|
+
multitool plugins configure
|
|
172
|
+
|
|
173
|
+
To automatically apply configuration changes after saving and closing the file
|
|
174
|
+
(install new plugins, update existing plugins, and prune removed plugins), use::
|
|
160
175
|
|
|
161
176
|
multitool plugins configure -a
|
|
162
177
|
|
|
163
178
|
Example configuration::
|
|
164
179
|
|
|
165
180
|
[sources]
|
|
166
|
-
|
|
181
|
+
multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
|
|
167
182
|
|
|
168
|
-
|
|
183
|
+
Configured repositories are cloned into::
|
|
169
184
|
|
|
170
185
|
~/.multitool/plugins/
|
|
171
186
|
|
|
172
|
-
|
|
187
|
+
Multiple repositories may be configured as long as each source key is unique.
|
|
173
188
|
|
|
174
189
|
^^^^^^^^^^
|
|
175
190
|
Installing
|
|
@@ -177,14 +192,14 @@ Installing
|
|
|
177
192
|
|
|
178
193
|
Install the example plugins::
|
|
179
194
|
|
|
180
|
-
|
|
195
|
+
multitool plugins add https://github.com/mdelotavo/multitool-plugins.git
|
|
181
196
|
|
|
182
197
|
multitool plugins update
|
|
183
|
-
|
|
198
|
+
|
|
184
199
|
multitool plugins show
|
|
185
|
-
multitool plugins show -n
|
|
186
|
-
multitool plugins show -n
|
|
187
|
-
multitool plugins show -n
|
|
200
|
+
multitool plugins show -n multitool-plugins
|
|
201
|
+
multitool plugins show -n multitool-plugins --show-commit-only
|
|
202
|
+
multitool plugins show -n multitool-plugins --show-dependencies-only
|
|
188
203
|
|
|
189
204
|
multitool run examples -h
|
|
190
205
|
|
|
@@ -219,7 +234,7 @@ the remaining packages manually. For example::
|
|
|
219
234
|
|
|
220
235
|
Updating plugins
|
|
221
236
|
----------------
|
|
222
|
-
Updating
|
|
237
|
+
Updating multitool-plugins... Done
|
|
223
238
|
Updating test... Skipped Git pull (not a Git repository)
|
|
224
239
|
|
|
225
240
|
Checking Python package dependencies
|
|
@@ -247,10 +262,20 @@ You can also display a plugin's declared dependencies at any time::
|
|
|
247
262
|
Pruning
|
|
248
263
|
^^^^^^^
|
|
249
264
|
|
|
250
|
-
|
|
265
|
+
To uninstall a plugin repository, first remove it from the configured sources::
|
|
266
|
+
|
|
267
|
+
multitool plugins remove multitool-plugins
|
|
268
|
+
|
|
269
|
+
Then remove any local repositories that are no longer configured::
|
|
251
270
|
|
|
252
271
|
multitool plugins prune
|
|
253
272
|
|
|
273
|
+
Alternatively, running::
|
|
274
|
+
|
|
275
|
+
multitool plugins configure -a
|
|
276
|
+
|
|
277
|
+
automatically prunes repositories removed from the configuration.
|
|
278
|
+
|
|
254
279
|
^^^^^^^
|
|
255
280
|
Showing
|
|
256
281
|
^^^^^^^
|
|
@@ -274,32 +299,6 @@ If a plugin fails to install or load, check the log file::
|
|
|
274
299
|
|
|
275
300
|
It contains installation, dependency, and Git-related errors.
|
|
276
301
|
|
|
277
|
-
-----------
|
|
278
|
-
Limitations
|
|
279
|
-
-----------
|
|
280
|
-
|
|
281
|
-
Plugin command names must be unique across all installed repositories.
|
|
282
|
-
|
|
283
|
-
To avoid naming conflicts, plugin modules should follow the convention of
|
|
284
|
-
including the repository owner and repository name in the command name.
|
|
285
|
-
|
|
286
|
-
For example, a repository configured as::
|
|
287
|
-
|
|
288
|
-
[sources]
|
|
289
|
-
mdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
|
|
290
|
-
|
|
291
|
-
should expose commands using a unique name such as::
|
|
292
|
-
|
|
293
|
-
mdelotavo-multitool-plugins
|
|
294
|
-
|
|
295
|
-
This reduces the likelihood of collisions when multiple repositories expose
|
|
296
|
-
plugins with the same command name.
|
|
297
|
-
|
|
298
|
-
If duplicate command names are detected, Multitool will display an error
|
|
299
|
-
prompting you to delete the detected conflicting plugin repositories and, if
|
|
300
|
-
applicable, remove the corresponding remote sources from your plugin
|
|
301
|
-
configuration.
|
|
302
|
-
|
|
303
302
|
|
|
304
303
|
.. _`click`: https://click.palletsprojects.com/
|
|
305
304
|
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
@@ -16,7 +16,9 @@ multitool.egg-info/dependency_links.txt
|
|
|
16
16
|
multitool.egg-info/entry_points.txt
|
|
17
17
|
multitool.egg-info/requires.txt
|
|
18
18
|
multitool.egg-info/top_level.txt
|
|
19
|
+
multitool/bootstrap/commands.py
|
|
19
20
|
multitool/plugins/__init__.py
|
|
20
21
|
multitool/plugins/commands.py
|
|
22
|
+
multitool/run/commands.py
|
|
21
23
|
tests/__init__.py
|
|
22
24
|
tests/test_utils.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "multitool"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.9.0"
|
|
8
8
|
description = "Create and run plugin-based command-line tools."
|
|
9
9
|
readme = { file = "README.rst", content-type = "text/x-rst" }
|
|
10
10
|
requires-python = ">=3.7"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|