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.
Files changed (26) hide show
  1. {multitool-0.8.0 → multitool-0.9.0}/PKG-INFO +45 -46
  2. {multitool-0.8.0 → multitool-0.9.0}/README.rst +44 -45
  3. {multitool-0.8.0 → multitool-0.9.0}/multitool/__init__.py +1 -1
  4. {multitool-0.8.0 → multitool-0.9.0}/multitool/__main__.py +4 -8
  5. multitool-0.9.0/multitool/bootstrap/commands.py +57 -0
  6. {multitool-0.8.0 → multitool-0.9.0}/multitool/console.py +1 -1
  7. {multitool-0.8.0 → multitool-0.9.0}/multitool/plugins/commands.py +140 -58
  8. multitool-0.9.0/multitool/run/commands.py +9 -0
  9. {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/PKG-INFO +45 -46
  10. {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/SOURCES.txt +2 -0
  11. {multitool-0.8.0 → multitool-0.9.0}/pyproject.toml +1 -1
  12. {multitool-0.8.0 → multitool-0.9.0}/LICENSE +0 -0
  13. {multitool-0.8.0 → multitool-0.9.0}/multitool/cls.py +0 -0
  14. {multitool-0.8.0 → multitool-0.9.0}/multitool/exceptions.py +0 -0
  15. {multitool-0.8.0 → multitool-0.9.0}/multitool/plugins/__init__.py +0 -0
  16. {multitool-0.8.0 → multitool-0.9.0}/multitool/silent.py +0 -0
  17. {multitool-0.8.0 → multitool-0.9.0}/multitool/utils.py +0 -0
  18. {multitool-0.8.0 → multitool-0.9.0}/multitool/utils_init.py +0 -0
  19. {multitool-0.8.0 → multitool-0.9.0}/multitool/verbose.py +0 -0
  20. {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/dependency_links.txt +0 -0
  21. {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/entry_points.txt +0 -0
  22. {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/requires.txt +0 -0
  23. {multitool-0.8.0 → multitool-0.9.0}/multitool.egg-info/top_level.txt +0 -0
  24. {multitool-0.8.0 → multitool-0.9.0}/setup.cfg +0 -0
  25. {multitool-0.8.0 → multitool-0.9.0}/tests/__init__.py +0 -0
  26. {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.8.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
- plugins Manage plugin repositories.
77
- run Run installed plugin commands.
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
- Configure plugin repositories with::
149
+ Add a plugin repository with::
149
150
 
150
- multitool plugins configure
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
- This opens your editor to modify the plugin configuration. After saving your
153
- changes, apply them with::
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
- To automatically apply the configuration changes after saving and closing the
158
- file (install new plugins, update existing plugins, and prune removed plugins),
159
- use::
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
- mdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
181
+ multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
167
182
 
168
- After saving, Multitool clones each configured repository into::
183
+ Configured repositories are cloned into::
169
184
 
170
185
  ~/.multitool/plugins/
171
186
 
172
- You can configure multiple repositories as long as each key is unique.
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
- echo -e '[sources]\nmdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git' >> ~/.multitool/plugins/config
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 mdelotavo-multitool-plugins
186
- multitool plugins show -n mdelotavo-multitool-plugins --show-commit-only
187
- multitool plugins show -n mdelotavo-multitool-plugins --show-dependencies-only
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 mdelotavo-multitool-plugins... Done
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
- Remove repositories no longer listed in the configuration::
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
- plugins Manage plugin repositories.
53
- run Run installed plugin commands.
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
- Configure plugin repositories with::
125
+ Add a plugin repository with::
125
126
 
126
- multitool plugins configure
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
- This opens your editor to modify the plugin configuration. After saving your
129
- changes, apply them with::
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
- To automatically apply the configuration changes after saving and closing the
134
- file (install new plugins, update existing plugins, and prune removed plugins),
135
- use::
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
- mdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
157
+ multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
143
158
 
144
- After saving, Multitool clones each configured repository into::
159
+ Configured repositories are cloned into::
145
160
 
146
161
  ~/.multitool/plugins/
147
162
 
148
- You can configure multiple repositories as long as each key is unique.
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
- echo -e '[sources]\nmdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git' >> ~/.multitool/plugins/config
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 mdelotavo-multitool-plugins
162
- multitool plugins show -n mdelotavo-multitool-plugins --show-commit-only
163
- multitool plugins show -n mdelotavo-multitool-plugins --show-dependencies-only
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 mdelotavo-multitool-plugins... Done
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
- Remove repositories no longer listed in the configuration::
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.8.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.utils import (configure_root_logger, for_each_file, load_plugins)
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}")
@@ -1,6 +1,6 @@
1
- from dataclasses import dataclass, fields, replace
2
1
  import builtins
3
2
  import sys
3
+ from dataclasses import dataclass, fields, replace
4
4
 
5
5
 
6
6
  @dataclass(slots=True)
@@ -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 config(section="sources"):
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 config().items():
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 = config()
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 = config()
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(f'Created plugin "{name}" at:')
571
- console.echo(f" {root}")
572
- console.echo()
573
- console.echo("Generated module:")
574
- console.echo(f" {module}.py")
575
- console.echo()
576
- console.echo("Next steps:")
577
- console.echo(" • Add commands to:")
578
- console.echo(f" {module}.py")
579
- console.echo()
580
- console.echo(" • To use this plugin locally:")
581
- console.echo(f" Copy the plugin directory into another {APP}")
582
- console.echo(" plugins directory:")
583
- console.echo(f" ~/.{APP}/plugins/")
584
- console.echo()
585
- console.echo(" • To distribute this plugin:")
586
- console.echo(" 1. Initialize a Git repository.")
587
- console.echo(" 2. Commit and push it to a remote source")
588
- console.echo(" (GitHub, GitLab, or another Git server).")
589
- console.echo(" 3. Add the repository URL to your plugin config:")
590
- console.echo(f" {APP} plugins configure")
591
- console.echo()
592
- console.echo(" Example:")
593
- console.echo()
594
- console.echo(" [sources]")
595
- console.echo(f" {name} = https://github.com/<user>/{name}.git")
596
- console.echo()
597
- console.echo(" HTTPS and SSH Git URLs are supported.")
598
- console.echo()
599
- console.echo(" 4. Install or update plugins:")
600
- console.echo(f" {APP} plugins update")
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
+ )
@@ -0,0 +1,9 @@
1
+ import click
2
+
3
+ from multitool.cls import AliasedGroup
4
+
5
+
6
+ @click.group(cls=AliasedGroup)
7
+ def run():
8
+ """Run installed plugin commands."""
9
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multitool
3
- Version: 0.8.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
- plugins Manage plugin repositories.
77
- run Run installed plugin commands.
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
- Configure plugin repositories with::
149
+ Add a plugin repository with::
149
150
 
150
- multitool plugins configure
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
- This opens your editor to modify the plugin configuration. After saving your
153
- changes, apply them with::
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
- To automatically apply the configuration changes after saving and closing the
158
- file (install new plugins, update existing plugins, and prune removed plugins),
159
- use::
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
- mdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
181
+ multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git
167
182
 
168
- After saving, Multitool clones each configured repository into::
183
+ Configured repositories are cloned into::
169
184
 
170
185
  ~/.multitool/plugins/
171
186
 
172
- You can configure multiple repositories as long as each key is unique.
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
- echo -e '[sources]\nmdelotavo-multitool-plugins = https://github.com/mdelotavo/multitool-plugins.git' >> ~/.multitool/plugins/config
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 mdelotavo-multitool-plugins
186
- multitool plugins show -n mdelotavo-multitool-plugins --show-commit-only
187
- multitool plugins show -n mdelotavo-multitool-plugins --show-dependencies-only
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 mdelotavo-multitool-plugins... Done
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
- Remove repositories no longer listed in the configuration::
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.8.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