multitool 0.6.0__tar.gz → 0.7.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.7.0/PKG-INFO +278 -0
- multitool-0.7.0/README.rst +255 -0
- multitool-0.7.0/multitool/__init__.py +23 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/__main__.py +23 -12
- multitool-0.7.0/multitool/console.py +37 -0
- multitool-0.7.0/multitool/plugins/commands.py +425 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/utils.py +56 -46
- multitool-0.7.0/multitool/utils_init.py +17 -0
- multitool-0.7.0/multitool.egg-info/PKG-INFO +278 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool.egg-info/SOURCES.txt +1 -0
- {multitool-0.6.0 → multitool-0.7.0}/pyproject.toml +2 -2
- multitool-0.6.0/PKG-INFO +0 -174
- multitool-0.6.0/README.rst +0 -151
- multitool-0.6.0/multitool/__init__.py +0 -23
- multitool-0.6.0/multitool/console.py +0 -25
- multitool-0.6.0/multitool/plugins/commands.py +0 -224
- multitool-0.6.0/multitool.egg-info/PKG-INFO +0 -174
- {multitool-0.6.0 → multitool-0.7.0}/LICENSE +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/cls.py +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/exceptions.py +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/plugins/__init__.py +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/silent.py +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool/verbose.py +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool.egg-info/dependency_links.txt +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool.egg-info/entry_points.txt +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool.egg-info/requires.txt +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/multitool.egg-info/top_level.txt +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/setup.cfg +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/tests/__init__.py +0 -0
- {multitool-0.6.0 → multitool-0.7.0}/tests/test_utils.py +0 -0
multitool-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multitool
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Create and run plugin-based command-line tools.
|
|
5
|
+
Author-email: Matthew Delotavo <matthew.t.delotavo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mdelotavo/multitool
|
|
8
|
+
Project-URL: Repository, https://github.com/mdelotavo/multitool
|
|
9
|
+
Keywords: cli,tooling,automation
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: click>=8.1.3
|
|
19
|
+
Requires-Dist: click-aliases>=1.0.1
|
|
20
|
+
Requires-Dist: click-option-group>=0.5.5
|
|
21
|
+
Requires-Dist: GitPython>=3.1.30
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
multitool
|
|
25
|
+
=========
|
|
26
|
+
|
|
27
|
+
Create and run plugin-based command-line tools.
|
|
28
|
+
|
|
29
|
+
-----
|
|
30
|
+
TL;DR
|
|
31
|
+
-----
|
|
32
|
+
|
|
33
|
+
This section shows how to start prototyping plugins on your local machine.
|
|
34
|
+
See the sections below to learn how to distribute your commands as remote
|
|
35
|
+
plugins that can be installed and updated from Git repositories.
|
|
36
|
+
|
|
37
|
+
Create a new local plugin::
|
|
38
|
+
|
|
39
|
+
multitool plugins new test
|
|
40
|
+
|
|
41
|
+
Show the generated command help::
|
|
42
|
+
|
|
43
|
+
multitool run test hello --help
|
|
44
|
+
|
|
45
|
+
Run the example command::
|
|
46
|
+
|
|
47
|
+
multitool run test hello \
|
|
48
|
+
"Hello, World!" \
|
|
49
|
+
--count 3 \
|
|
50
|
+
--format json \
|
|
51
|
+
-vv \
|
|
52
|
+
--enabled \
|
|
53
|
+
--tag alpha \
|
|
54
|
+
--tag beta \
|
|
55
|
+
--output result.json
|
|
56
|
+
|
|
57
|
+
Edit the generated source code::
|
|
58
|
+
|
|
59
|
+
vim ~/.multitool/plugins/test/plugin_*.py
|
|
60
|
+
|
|
61
|
+
-----
|
|
62
|
+
Usage
|
|
63
|
+
-----
|
|
64
|
+
|
|
65
|
+
.. code-block:: text
|
|
66
|
+
|
|
67
|
+
Usage: multitool [OPTIONS] COMMAND [ARGS]...
|
|
68
|
+
|
|
69
|
+
Create and run plugin-based command-line tools.
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
-V, --version Show the version and exit.
|
|
73
|
+
-h, --help Show this message and exit.
|
|
74
|
+
|
|
75
|
+
Commands:
|
|
76
|
+
plugins Manage plugin repositories.
|
|
77
|
+
run Run installed plugin commands.
|
|
78
|
+
|
|
79
|
+
----------------
|
|
80
|
+
Managing plugins
|
|
81
|
+
----------------
|
|
82
|
+
|
|
83
|
+
Plugins are distributed as Git repositories containing Click commands.
|
|
84
|
+
|
|
85
|
+
The ``plugins`` command manages plugin repositories, while installed plugin
|
|
86
|
+
commands are available under ``multitool run``.
|
|
87
|
+
|
|
88
|
+
Git is required to install or update plugins. If Git is unavailable, the
|
|
89
|
+
``plugins`` command cannot install repositories, although plugins can still be
|
|
90
|
+
installed manually by copying them into::
|
|
91
|
+
|
|
92
|
+
~/.multitool/plugins/PLUGIN_NAME/
|
|
93
|
+
|
|
94
|
+
^^^^^^^^^^^^^^^^
|
|
95
|
+
Creating plugins
|
|
96
|
+
^^^^^^^^^^^^^^^^
|
|
97
|
+
|
|
98
|
+
Create a new plugin scaffold with::
|
|
99
|
+
|
|
100
|
+
multitool plugins new PLUGIN_NAME
|
|
101
|
+
|
|
102
|
+
This creates a local plugin repository under::
|
|
103
|
+
|
|
104
|
+
~/.multitool/plugins/PLUGIN_NAME/
|
|
105
|
+
|
|
106
|
+
The generated structure includes::
|
|
107
|
+
|
|
108
|
+
PLUGIN_NAME/
|
|
109
|
+
├── __init__.py
|
|
110
|
+
├── plugin_<unique-id>.py
|
|
111
|
+
├── multitool-info.json
|
|
112
|
+
├── README.md
|
|
113
|
+
└── LICENSE
|
|
114
|
+
|
|
115
|
+
The generated plugin contains a Click command group named
|
|
116
|
+
``PLUGIN_NAME`` and an example ``hello`` command. Add additional commands
|
|
117
|
+
to the generated ``plugin_<unique-id>.py`` module.
|
|
118
|
+
|
|
119
|
+
Test the plugin locally with::
|
|
120
|
+
|
|
121
|
+
multitool run PLUGIN_NAME -h
|
|
122
|
+
|
|
123
|
+
To distribute the plugin:
|
|
124
|
+
|
|
125
|
+
1. Initialize the plugin directory as a Git repository::
|
|
126
|
+
|
|
127
|
+
cd ~/.multitool/plugins/PLUGIN_NAME
|
|
128
|
+
git init
|
|
129
|
+
|
|
130
|
+
2. Commit and push it to a remote Git repository such as GitHub or GitLab.
|
|
131
|
+
|
|
132
|
+
3. Add the repository URL to the Multitool configuration::
|
|
133
|
+
|
|
134
|
+
[sources]
|
|
135
|
+
PLUGIN_NAME = https://github.com/<user>/PLUGIN_NAME.git
|
|
136
|
+
|
|
137
|
+
4. Install or update plugins::
|
|
138
|
+
|
|
139
|
+
multitool plugins update
|
|
140
|
+
|
|
141
|
+
Alternatively, copy the plugin directory directly into another Multitool
|
|
142
|
+
plugins directory to use it locally.
|
|
143
|
+
|
|
144
|
+
^^^^^^^^^^^
|
|
145
|
+
Configuring
|
|
146
|
+
^^^^^^^^^^^
|
|
147
|
+
|
|
148
|
+
Configure plugin repositories with::
|
|
149
|
+
|
|
150
|
+
multitool plugins configure -a
|
|
151
|
+
|
|
152
|
+
This opens your editor to modify the plugin configuration. Omit ``-a`` if you
|
|
153
|
+
don't want changes applied automatically.
|
|
154
|
+
|
|
155
|
+
Example configuration::
|
|
156
|
+
|
|
157
|
+
[sources]
|
|
158
|
+
public = https://github.com/mdelotavo/multitool-plugins.git
|
|
159
|
+
|
|
160
|
+
After saving, Multitool clones each configured repository into::
|
|
161
|
+
|
|
162
|
+
~/.multitool/plugins/
|
|
163
|
+
|
|
164
|
+
You can configure multiple repositories as long as each key is unique.
|
|
165
|
+
|
|
166
|
+
^^^^^^^^^^
|
|
167
|
+
Quickstart
|
|
168
|
+
^^^^^^^^^^
|
|
169
|
+
|
|
170
|
+
Install the example plugins::
|
|
171
|
+
|
|
172
|
+
echo -e '[sources]\npublic = https://github.com/mdelotavo/multitool-plugins.git' >> ~/.multitool/plugins/config
|
|
173
|
+
|
|
174
|
+
multitool plugins update
|
|
175
|
+
multitool plugins show
|
|
176
|
+
multitool plugins show -n public
|
|
177
|
+
multitool plugins show -n public --show-commit-only
|
|
178
|
+
multitool plugins show -n public --show-dependencies-only
|
|
179
|
+
pip3 install $(multitool plugins show -n public --show-dependencies-only)
|
|
180
|
+
|
|
181
|
+
multitool run examples -h
|
|
182
|
+
|
|
183
|
+
^^^^^^^^
|
|
184
|
+
Updating
|
|
185
|
+
^^^^^^^^
|
|
186
|
+
|
|
187
|
+
Install new plugins and update existing ones::
|
|
188
|
+
|
|
189
|
+
multitool plugins update
|
|
190
|
+
|
|
191
|
+
^^^^^^^
|
|
192
|
+
Pruning
|
|
193
|
+
^^^^^^^
|
|
194
|
+
|
|
195
|
+
Remove repositories no longer listed in the configuration::
|
|
196
|
+
|
|
197
|
+
multitool plugins prune
|
|
198
|
+
|
|
199
|
+
^^^^^^^
|
|
200
|
+
Showing
|
|
201
|
+
^^^^^^^
|
|
202
|
+
|
|
203
|
+
Show configured repositories::
|
|
204
|
+
|
|
205
|
+
multitool plugins show
|
|
206
|
+
|
|
207
|
+
Or inspect a specific repository::
|
|
208
|
+
|
|
209
|
+
multitool plugins show -n PLUGIN_NAME --show-commit-only
|
|
210
|
+
multitool plugins show -n PLUGIN_NAME --show-dependencies-only
|
|
211
|
+
|
|
212
|
+
If a plugin declares Python dependencies in ``multitool-info.json``, install
|
|
213
|
+
them with::
|
|
214
|
+
|
|
215
|
+
pip3 install $(multitool plugins show -n PLUGIN_NAME --show-dependencies-only)
|
|
216
|
+
|
|
217
|
+
---------------
|
|
218
|
+
Troubleshooting
|
|
219
|
+
---------------
|
|
220
|
+
|
|
221
|
+
If a plugin fails to install or load, check the log file::
|
|
222
|
+
|
|
223
|
+
~/.multitool/multitool.log
|
|
224
|
+
|
|
225
|
+
It contains installation, dependency, and Git-related errors.
|
|
226
|
+
|
|
227
|
+
-----------
|
|
228
|
+
Limitations
|
|
229
|
+
-----------
|
|
230
|
+
|
|
231
|
+
Plugin command names must be unique across all installed repositories.
|
|
232
|
+
|
|
233
|
+
To avoid naming conflicts, plugin modules should follow the convention of
|
|
234
|
+
including the repository owner and repository name in the command name.
|
|
235
|
+
|
|
236
|
+
For example, a repository configured as::
|
|
237
|
+
|
|
238
|
+
[sources]
|
|
239
|
+
public = https://github.com/mdelotavo/multitool-plugins.git
|
|
240
|
+
|
|
241
|
+
should expose commands using a unique name such as::
|
|
242
|
+
|
|
243
|
+
mdelotavo-multitool-plugins
|
|
244
|
+
|
|
245
|
+
This prevents duplicate command names when multiple repositories provide
|
|
246
|
+
plugins with the same module name. If two plugins expose commands with the
|
|
247
|
+
same name, only one can be loaded and the duplicate plugin will be skipped.
|
|
248
|
+
|
|
249
|
+
If two repositories expose the same command name, Multitool will fail to load
|
|
250
|
+
the duplicate plugin and raise an error similar to::
|
|
251
|
+
|
|
252
|
+
Duplicate plugin command "examples" found in
|
|
253
|
+
/home/user/.multitool/plugins/PLUGIN_NAME/__init__.py.
|
|
254
|
+
Already loaded from plugins_modules.examples
|
|
255
|
+
|
|
256
|
+
To resolve the issue, remove the conflicting plugin repository from your local
|
|
257
|
+
plugins directory::
|
|
258
|
+
|
|
259
|
+
~/.multitool/plugins/PLUGIN_NAME/
|
|
260
|
+
|
|
261
|
+
and remove the repository from the configured sources.
|
|
262
|
+
|
|
263
|
+
You can edit the configuration file manually::
|
|
264
|
+
|
|
265
|
+
~/.multitool/plugins/config
|
|
266
|
+
|
|
267
|
+
or open it using::
|
|
268
|
+
|
|
269
|
+
multitool plugins configure
|
|
270
|
+
|
|
271
|
+
After removing the conflicting repository, update the installed plugins::
|
|
272
|
+
|
|
273
|
+
multitool plugins update
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
.. _`click`: https://click.palletsprojects.com/
|
|
277
|
+
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
278
|
+
.. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
multitool
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
Create and run plugin-based command-line tools.
|
|
5
|
+
|
|
6
|
+
-----
|
|
7
|
+
TL;DR
|
|
8
|
+
-----
|
|
9
|
+
|
|
10
|
+
This section shows how to start prototyping plugins on your local machine.
|
|
11
|
+
See the sections below to learn how to distribute your commands as remote
|
|
12
|
+
plugins that can be installed and updated from Git repositories.
|
|
13
|
+
|
|
14
|
+
Create a new local plugin::
|
|
15
|
+
|
|
16
|
+
multitool plugins new test
|
|
17
|
+
|
|
18
|
+
Show the generated command help::
|
|
19
|
+
|
|
20
|
+
multitool run test hello --help
|
|
21
|
+
|
|
22
|
+
Run the example command::
|
|
23
|
+
|
|
24
|
+
multitool run test hello \
|
|
25
|
+
"Hello, World!" \
|
|
26
|
+
--count 3 \
|
|
27
|
+
--format json \
|
|
28
|
+
-vv \
|
|
29
|
+
--enabled \
|
|
30
|
+
--tag alpha \
|
|
31
|
+
--tag beta \
|
|
32
|
+
--output result.json
|
|
33
|
+
|
|
34
|
+
Edit the generated source code::
|
|
35
|
+
|
|
36
|
+
vim ~/.multitool/plugins/test/plugin_*.py
|
|
37
|
+
|
|
38
|
+
-----
|
|
39
|
+
Usage
|
|
40
|
+
-----
|
|
41
|
+
|
|
42
|
+
.. code-block:: text
|
|
43
|
+
|
|
44
|
+
Usage: multitool [OPTIONS] COMMAND [ARGS]...
|
|
45
|
+
|
|
46
|
+
Create and run plugin-based command-line tools.
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
-V, --version Show the version and exit.
|
|
50
|
+
-h, --help Show this message and exit.
|
|
51
|
+
|
|
52
|
+
Commands:
|
|
53
|
+
plugins Manage plugin repositories.
|
|
54
|
+
run Run installed plugin commands.
|
|
55
|
+
|
|
56
|
+
----------------
|
|
57
|
+
Managing plugins
|
|
58
|
+
----------------
|
|
59
|
+
|
|
60
|
+
Plugins are distributed as Git repositories containing Click commands.
|
|
61
|
+
|
|
62
|
+
The ``plugins`` command manages plugin repositories, while installed plugin
|
|
63
|
+
commands are available under ``multitool run``.
|
|
64
|
+
|
|
65
|
+
Git is required to install or update plugins. If Git is unavailable, the
|
|
66
|
+
``plugins`` command cannot install repositories, although plugins can still be
|
|
67
|
+
installed manually by copying them into::
|
|
68
|
+
|
|
69
|
+
~/.multitool/plugins/PLUGIN_NAME/
|
|
70
|
+
|
|
71
|
+
^^^^^^^^^^^^^^^^
|
|
72
|
+
Creating plugins
|
|
73
|
+
^^^^^^^^^^^^^^^^
|
|
74
|
+
|
|
75
|
+
Create a new plugin scaffold with::
|
|
76
|
+
|
|
77
|
+
multitool plugins new PLUGIN_NAME
|
|
78
|
+
|
|
79
|
+
This creates a local plugin repository under::
|
|
80
|
+
|
|
81
|
+
~/.multitool/plugins/PLUGIN_NAME/
|
|
82
|
+
|
|
83
|
+
The generated structure includes::
|
|
84
|
+
|
|
85
|
+
PLUGIN_NAME/
|
|
86
|
+
├── __init__.py
|
|
87
|
+
├── plugin_<unique-id>.py
|
|
88
|
+
├── multitool-info.json
|
|
89
|
+
├── README.md
|
|
90
|
+
└── LICENSE
|
|
91
|
+
|
|
92
|
+
The generated plugin contains a Click command group named
|
|
93
|
+
``PLUGIN_NAME`` and an example ``hello`` command. Add additional commands
|
|
94
|
+
to the generated ``plugin_<unique-id>.py`` module.
|
|
95
|
+
|
|
96
|
+
Test the plugin locally with::
|
|
97
|
+
|
|
98
|
+
multitool run PLUGIN_NAME -h
|
|
99
|
+
|
|
100
|
+
To distribute the plugin:
|
|
101
|
+
|
|
102
|
+
1. Initialize the plugin directory as a Git repository::
|
|
103
|
+
|
|
104
|
+
cd ~/.multitool/plugins/PLUGIN_NAME
|
|
105
|
+
git init
|
|
106
|
+
|
|
107
|
+
2. Commit and push it to a remote Git repository such as GitHub or GitLab.
|
|
108
|
+
|
|
109
|
+
3. Add the repository URL to the Multitool configuration::
|
|
110
|
+
|
|
111
|
+
[sources]
|
|
112
|
+
PLUGIN_NAME = https://github.com/<user>/PLUGIN_NAME.git
|
|
113
|
+
|
|
114
|
+
4. Install or update plugins::
|
|
115
|
+
|
|
116
|
+
multitool plugins update
|
|
117
|
+
|
|
118
|
+
Alternatively, copy the plugin directory directly into another Multitool
|
|
119
|
+
plugins directory to use it locally.
|
|
120
|
+
|
|
121
|
+
^^^^^^^^^^^
|
|
122
|
+
Configuring
|
|
123
|
+
^^^^^^^^^^^
|
|
124
|
+
|
|
125
|
+
Configure plugin repositories with::
|
|
126
|
+
|
|
127
|
+
multitool plugins configure -a
|
|
128
|
+
|
|
129
|
+
This opens your editor to modify the plugin configuration. Omit ``-a`` if you
|
|
130
|
+
don't want changes applied automatically.
|
|
131
|
+
|
|
132
|
+
Example configuration::
|
|
133
|
+
|
|
134
|
+
[sources]
|
|
135
|
+
public = https://github.com/mdelotavo/multitool-plugins.git
|
|
136
|
+
|
|
137
|
+
After saving, Multitool clones each configured repository into::
|
|
138
|
+
|
|
139
|
+
~/.multitool/plugins/
|
|
140
|
+
|
|
141
|
+
You can configure multiple repositories as long as each key is unique.
|
|
142
|
+
|
|
143
|
+
^^^^^^^^^^
|
|
144
|
+
Quickstart
|
|
145
|
+
^^^^^^^^^^
|
|
146
|
+
|
|
147
|
+
Install the example plugins::
|
|
148
|
+
|
|
149
|
+
echo -e '[sources]\npublic = https://github.com/mdelotavo/multitool-plugins.git' >> ~/.multitool/plugins/config
|
|
150
|
+
|
|
151
|
+
multitool plugins update
|
|
152
|
+
multitool plugins show
|
|
153
|
+
multitool plugins show -n public
|
|
154
|
+
multitool plugins show -n public --show-commit-only
|
|
155
|
+
multitool plugins show -n public --show-dependencies-only
|
|
156
|
+
pip3 install $(multitool plugins show -n public --show-dependencies-only)
|
|
157
|
+
|
|
158
|
+
multitool run examples -h
|
|
159
|
+
|
|
160
|
+
^^^^^^^^
|
|
161
|
+
Updating
|
|
162
|
+
^^^^^^^^
|
|
163
|
+
|
|
164
|
+
Install new plugins and update existing ones::
|
|
165
|
+
|
|
166
|
+
multitool plugins update
|
|
167
|
+
|
|
168
|
+
^^^^^^^
|
|
169
|
+
Pruning
|
|
170
|
+
^^^^^^^
|
|
171
|
+
|
|
172
|
+
Remove repositories no longer listed in the configuration::
|
|
173
|
+
|
|
174
|
+
multitool plugins prune
|
|
175
|
+
|
|
176
|
+
^^^^^^^
|
|
177
|
+
Showing
|
|
178
|
+
^^^^^^^
|
|
179
|
+
|
|
180
|
+
Show configured repositories::
|
|
181
|
+
|
|
182
|
+
multitool plugins show
|
|
183
|
+
|
|
184
|
+
Or inspect a specific repository::
|
|
185
|
+
|
|
186
|
+
multitool plugins show -n PLUGIN_NAME --show-commit-only
|
|
187
|
+
multitool plugins show -n PLUGIN_NAME --show-dependencies-only
|
|
188
|
+
|
|
189
|
+
If a plugin declares Python dependencies in ``multitool-info.json``, install
|
|
190
|
+
them with::
|
|
191
|
+
|
|
192
|
+
pip3 install $(multitool plugins show -n PLUGIN_NAME --show-dependencies-only)
|
|
193
|
+
|
|
194
|
+
---------------
|
|
195
|
+
Troubleshooting
|
|
196
|
+
---------------
|
|
197
|
+
|
|
198
|
+
If a plugin fails to install or load, check the log file::
|
|
199
|
+
|
|
200
|
+
~/.multitool/multitool.log
|
|
201
|
+
|
|
202
|
+
It contains installation, dependency, and Git-related errors.
|
|
203
|
+
|
|
204
|
+
-----------
|
|
205
|
+
Limitations
|
|
206
|
+
-----------
|
|
207
|
+
|
|
208
|
+
Plugin command names must be unique across all installed repositories.
|
|
209
|
+
|
|
210
|
+
To avoid naming conflicts, plugin modules should follow the convention of
|
|
211
|
+
including the repository owner and repository name in the command name.
|
|
212
|
+
|
|
213
|
+
For example, a repository configured as::
|
|
214
|
+
|
|
215
|
+
[sources]
|
|
216
|
+
public = https://github.com/mdelotavo/multitool-plugins.git
|
|
217
|
+
|
|
218
|
+
should expose commands using a unique name such as::
|
|
219
|
+
|
|
220
|
+
mdelotavo-multitool-plugins
|
|
221
|
+
|
|
222
|
+
This prevents duplicate command names when multiple repositories provide
|
|
223
|
+
plugins with the same module name. If two plugins expose commands with the
|
|
224
|
+
same name, only one can be loaded and the duplicate plugin will be skipped.
|
|
225
|
+
|
|
226
|
+
If two repositories expose the same command name, Multitool will fail to load
|
|
227
|
+
the duplicate plugin and raise an error similar to::
|
|
228
|
+
|
|
229
|
+
Duplicate plugin command "examples" found in
|
|
230
|
+
/home/user/.multitool/plugins/PLUGIN_NAME/__init__.py.
|
|
231
|
+
Already loaded from plugins_modules.examples
|
|
232
|
+
|
|
233
|
+
To resolve the issue, remove the conflicting plugin repository from your local
|
|
234
|
+
plugins directory::
|
|
235
|
+
|
|
236
|
+
~/.multitool/plugins/PLUGIN_NAME/
|
|
237
|
+
|
|
238
|
+
and remove the repository from the configured sources.
|
|
239
|
+
|
|
240
|
+
You can edit the configuration file manually::
|
|
241
|
+
|
|
242
|
+
~/.multitool/plugins/config
|
|
243
|
+
|
|
244
|
+
or open it using::
|
|
245
|
+
|
|
246
|
+
multitool plugins configure
|
|
247
|
+
|
|
248
|
+
After removing the conflicting repository, update the installed plugins::
|
|
249
|
+
|
|
250
|
+
multitool plugins update
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
.. _`click`: https://click.palletsprojects.com/
|
|
254
|
+
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
255
|
+
.. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
APP = "multitool"
|
|
2
|
+
__version__ = "0.7.0"
|
|
3
|
+
description = "Create and run plugin-based command-line tools."
|
|
4
|
+
long_description = ("Create and run plugin-based command-line tools by dynamically loading Click commands from local or Git-managed plugin repositories.")
|
|
5
|
+
|
|
6
|
+
import builtins
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from multitool.utils_init import join_path
|
|
10
|
+
|
|
11
|
+
MULTITOOL_TOGGLE_SILENT = False
|
|
12
|
+
MULTITOOL_TOGGLE_VERBOSE = 0
|
|
13
|
+
|
|
14
|
+
MULTITOOL_DIRECTORY = join_path(Path.home(), ".multitool")
|
|
15
|
+
|
|
16
|
+
MULTITOOL_LOG_FILE = join_path(MULTITOOL_DIRECTORY, f"{APP}.log")
|
|
17
|
+
|
|
18
|
+
MULTITOOL_PLUGINS_DIRECTORY = join_path(MULTITOOL_DIRECTORY, "plugins")
|
|
19
|
+
MULTITOOL_PLUGINS_CONFIG_FILE = join_path(MULTITOOL_PLUGINS_DIRECTORY, "config")
|
|
20
|
+
MULTITOOL_PLUGINS_PATH = join_path(MULTITOOL_PLUGINS_DIRECTORY, "__init__.py")
|
|
21
|
+
|
|
22
|
+
builtins.MULTITOOL_TOGGLE_SILENT = MULTITOOL_TOGGLE_SILENT
|
|
23
|
+
builtins.MULTITOOL_TOGGLE_VERBOSE = MULTITOOL_TOGGLE_VERBOSE
|
|
@@ -8,37 +8,48 @@ from multitool import __version__ as version
|
|
|
8
8
|
from multitool.cls import AliasedGroup
|
|
9
9
|
from multitool.exceptions import wrap_with_exception_handling
|
|
10
10
|
from multitool.plugins.commands import plugins
|
|
11
|
-
from multitool.utils import (
|
|
11
|
+
from multitool.utils import (configure_logger, for_each_file, load_plugins)
|
|
12
12
|
|
|
13
|
-
CONTEXT_SETTINGS = dict(help_option_names=[
|
|
13
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@click.group(context_settings=CONTEXT_SETTINGS, cls=AliasedGroup, invoke_without_command=False, chain=False)
|
|
17
|
-
@click.version_option(version,
|
|
17
|
+
@click.version_option(version, "-V", "--version")
|
|
18
18
|
@click.pass_context
|
|
19
19
|
def cli(ctx):
|
|
20
|
-
"""
|
|
20
|
+
"""Create and run plugin-based command-line tools."""
|
|
21
21
|
ctx.ensure_object(dict)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
@click.group(cls=AliasedGroup)
|
|
25
|
+
def run():
|
|
26
|
+
"""Run installed plugin commands."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
24
30
|
@wrap_with_exception_handling
|
|
25
31
|
def main():
|
|
26
|
-
|
|
32
|
+
configure_logger(MULTITOOL_LOG_FILE)
|
|
27
33
|
|
|
28
|
-
cli_commands = {plugins}
|
|
34
|
+
cli_commands = {plugins, run}
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
run_commands = {}
|
|
37
|
+
|
|
38
|
+
for_each_file(
|
|
31
39
|
MULTITOOL_PLUGINS_DIRECTORY,
|
|
32
|
-
|
|
33
|
-
args=(
|
|
34
|
-
glob=
|
|
40
|
+
load_plugins,
|
|
41
|
+
args=(run_commands, ),
|
|
42
|
+
glob="[!.][!__]*/__init__.py",
|
|
35
43
|
)
|
|
36
44
|
|
|
45
|
+
for command in run_commands.values():
|
|
46
|
+
run.add_command(command)
|
|
47
|
+
|
|
37
48
|
for command in cli_commands:
|
|
38
49
|
cli.add_command(command)
|
|
39
50
|
|
|
40
51
|
cli(prog_name=APP, obj={})
|
|
41
52
|
|
|
42
53
|
|
|
43
|
-
if __name__ ==
|
|
44
|
-
main()
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields, replace
|
|
2
|
+
import builtins
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(slots=True)
|
|
7
|
+
class EchoOptions:
|
|
8
|
+
exit_status: int | None = None
|
|
9
|
+
silent: bool = False
|
|
10
|
+
verbosity: int = 0
|
|
11
|
+
level: int = 0
|
|
12
|
+
end: str = "\n"
|
|
13
|
+
flush: bool = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def echo(*msg, options: EchoOptions | None = None, **kwargs):
|
|
17
|
+
options = options or EchoOptions()
|
|
18
|
+
|
|
19
|
+
valid_fields = {f.name for f in fields(EchoOptions)}
|
|
20
|
+
|
|
21
|
+
for key, value in kwargs.items():
|
|
22
|
+
if key not in valid_fields:
|
|
23
|
+
raise TypeError(f"echo() got an unexpected keyword argument '{key}'")
|
|
24
|
+
options = replace(options, **{key: value})
|
|
25
|
+
|
|
26
|
+
if options.silent or builtins.MULTITOOL_TOGGLE_SILENT:
|
|
27
|
+
if options.exit_status is not None:
|
|
28
|
+
sys.exit(options.exit_status)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
verbosity = max(options.verbosity, builtins.MULTITOOL_TOGGLE_VERBOSE)
|
|
32
|
+
|
|
33
|
+
if verbosity >= options.level:
|
|
34
|
+
print(*msg, end=options.end, flush=options.flush)
|
|
35
|
+
|
|
36
|
+
if options.exit_status is not None:
|
|
37
|
+
sys.exit(options.exit_status)
|