multitool 0.3.2__tar.gz → 0.5.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.3.2 → multitool-0.5.0}/LICENSE +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/PKG-INFO +15 -10
- {multitool-0.3.2 → multitool-0.5.0}/README.rst +13 -8
- multitool-0.5.0/multitool/__init__.py +23 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool/__main__.py +8 -20
- {multitool-0.3.2 → multitool-0.5.0}/multitool/cls.py +1 -1
- multitool-0.5.0/multitool/console.py +23 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool/exceptions.py +1 -2
- {multitool-0.3.2 → multitool-0.5.0}/multitool/plugins/__init__.py +0 -0
- multitool-0.5.0/multitool/plugins/commands.py +266 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool/silent.py +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool/utils.py +79 -75
- {multitool-0.3.2 → multitool-0.5.0}/multitool/verbose.py +0 -1
- {multitool-0.3.2 → multitool-0.5.0}/multitool.egg-info/PKG-INFO +15 -10
- {multitool-0.3.2 → multitool-0.5.0}/multitool.egg-info/SOURCES.txt +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool.egg-info/dependency_links.txt +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool.egg-info/entry_points.txt +0 -0
- multitool-0.5.0/multitool.egg-info/requires.txt +4 -0
- {multitool-0.3.2 → multitool-0.5.0}/multitool.egg-info/top_level.txt +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/setup.cfg +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/setup.py +11 -20
- {multitool-0.3.2 → multitool-0.5.0}/tests/__init__.py +0 -0
- {multitool-0.3.2 → multitool-0.5.0}/tests/test_utils.py +0 -0
- multitool-0.3.2/multitool/__init__.py +0 -23
- multitool-0.3.2/multitool/console.py +0 -17
- multitool-0.3.2/multitool/plugins/commands.py +0 -231
- multitool-0.3.2/multitool.egg-info/requires.txt +0 -12
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: multitool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: General-purpose command-line interface with plugins support
|
|
5
5
|
Home-page: https://github.com/mdelotavo/multitool
|
|
6
6
|
Author: Matthew Delotavo
|
|
@@ -13,7 +13,7 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
|
|
19
19
|
multitool
|
|
@@ -25,9 +25,7 @@ Multitool is a general-purpose command-line interface with plugins support.
|
|
|
25
25
|
Why does this exist?
|
|
26
26
|
--------------------
|
|
27
27
|
|
|
28
|
-
I built this tool so
|
|
29
|
-
|
|
30
|
-
The plugins manager uses ``git`` to manage plugins installed from remote ``git`` repositories.
|
|
28
|
+
I built this tool so I can quickly create and distribute command-line tools for consulting work and personal use.
|
|
31
29
|
|
|
32
30
|
-----
|
|
33
31
|
Usage
|
|
@@ -49,9 +47,6 @@ Usage
|
|
|
49
47
|
Commands:
|
|
50
48
|
plugins Simple plugins manager for distributing commands.
|
|
51
49
|
|
|
52
|
-
.. ..
|
|
53
|
-
The plugins features are based off those found in the `apigeecli`_.
|
|
54
|
-
|
|
55
50
|
----------------
|
|
56
51
|
Managing plugins
|
|
57
52
|
----------------
|
|
@@ -123,7 +118,7 @@ This will install and update plugins.
|
|
|
123
118
|
Pruning
|
|
124
119
|
^^^^^^^
|
|
125
120
|
|
|
126
|
-
If you specified the ``-a`` option when running ``multitool plugins configure`` then the removal of plugins will occur automatically.
|
|
121
|
+
If you remove plugins from the config file or comment them out, and you then specified the ``-a`` option when running ``multitool plugins configure`` then the removal of plugins will occur automatically.
|
|
127
122
|
Otherwise you can run::
|
|
128
123
|
|
|
129
124
|
multitool plugins prune
|
|
@@ -147,6 +142,17 @@ In order for this to work, the plugin needs to have the ``Requires`` key in the
|
|
|
147
142
|
|
|
148
143
|
pip3 install $(multitool plugins show -n PLUGIN_NAME --show-dependencies-only)
|
|
149
144
|
|
|
145
|
+
^^^^^^^^^^^^^^^
|
|
146
|
+
Troubleshooting
|
|
147
|
+
^^^^^^^^^^^^^^^
|
|
148
|
+
|
|
149
|
+
If you encounter issues with plugins or commands, you can find more detailed debug information and error messages in the log file:
|
|
150
|
+
::
|
|
151
|
+
|
|
152
|
+
~/.multitool/multitool.log
|
|
153
|
+
|
|
154
|
+
Reviewing this file can help you diagnose installation problems, missing dependencies, or Git-related errors.
|
|
155
|
+
|
|
150
156
|
.. ..
|
|
151
157
|
----------------------------
|
|
152
158
|
Create a virtual environment
|
|
@@ -168,7 +174,6 @@ In order for this to work, the plugin needs to have the ``Requires`` key in the
|
|
|
168
174
|
deactivate
|
|
169
175
|
|
|
170
176
|
.. _`click`: https://click.palletsprojects.com/
|
|
171
|
-
.. _`apigeecli`: https://pypi.org/project/apigeecli/
|
|
172
177
|
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
173
178
|
.. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
|
|
174
179
|
|
|
@@ -7,9 +7,7 @@ Multitool is a general-purpose command-line interface with plugins support.
|
|
|
7
7
|
Why does this exist?
|
|
8
8
|
--------------------
|
|
9
9
|
|
|
10
|
-
I built this tool so
|
|
11
|
-
|
|
12
|
-
The plugins manager uses ``git`` to manage plugins installed from remote ``git`` repositories.
|
|
10
|
+
I built this tool so I can quickly create and distribute command-line tools for consulting work and personal use.
|
|
13
11
|
|
|
14
12
|
-----
|
|
15
13
|
Usage
|
|
@@ -31,9 +29,6 @@ Usage
|
|
|
31
29
|
Commands:
|
|
32
30
|
plugins Simple plugins manager for distributing commands.
|
|
33
31
|
|
|
34
|
-
.. ..
|
|
35
|
-
The plugins features are based off those found in the `apigeecli`_.
|
|
36
|
-
|
|
37
32
|
----------------
|
|
38
33
|
Managing plugins
|
|
39
34
|
----------------
|
|
@@ -105,7 +100,7 @@ This will install and update plugins.
|
|
|
105
100
|
Pruning
|
|
106
101
|
^^^^^^^
|
|
107
102
|
|
|
108
|
-
If you specified the ``-a`` option when running ``multitool plugins configure`` then the removal of plugins will occur automatically.
|
|
103
|
+
If you remove plugins from the config file or comment them out, and you then specified the ``-a`` option when running ``multitool plugins configure`` then the removal of plugins will occur automatically.
|
|
109
104
|
Otherwise you can run::
|
|
110
105
|
|
|
111
106
|
multitool plugins prune
|
|
@@ -129,6 +124,17 @@ In order for this to work, the plugin needs to have the ``Requires`` key in the
|
|
|
129
124
|
|
|
130
125
|
pip3 install $(multitool plugins show -n PLUGIN_NAME --show-dependencies-only)
|
|
131
126
|
|
|
127
|
+
^^^^^^^^^^^^^^^
|
|
128
|
+
Troubleshooting
|
|
129
|
+
^^^^^^^^^^^^^^^
|
|
130
|
+
|
|
131
|
+
If you encounter issues with plugins or commands, you can find more detailed debug information and error messages in the log file:
|
|
132
|
+
::
|
|
133
|
+
|
|
134
|
+
~/.multitool/multitool.log
|
|
135
|
+
|
|
136
|
+
Reviewing this file can help you diagnose installation problems, missing dependencies, or Git-related errors.
|
|
137
|
+
|
|
132
138
|
.. ..
|
|
133
139
|
----------------------------
|
|
134
140
|
Create a virtual environment
|
|
@@ -150,6 +156,5 @@ In order for this to work, the plugin needs to have the ``Requires`` key in the
|
|
|
150
156
|
deactivate
|
|
151
157
|
|
|
152
158
|
.. _`click`: https://click.palletsprojects.com/
|
|
153
|
-
.. _`apigeecli`: https://pypi.org/project/apigeecli/
|
|
154
159
|
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
155
160
|
.. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
APP = 'multitool'
|
|
2
|
+
__version__ = '0.5.0'
|
|
3
|
+
description = 'General-purpose command-line interface with plugins support'
|
|
4
|
+
# long_description = 'General-purpose command-line interface with plugins support'
|
|
5
|
+
|
|
6
|
+
import builtins
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from multitool.utils import join_path_components
|
|
10
|
+
|
|
11
|
+
MULTITOOL_TOGGLE_SILENT = False
|
|
12
|
+
MULTITOOL_TOGGLE_VERBOSE = 0
|
|
13
|
+
|
|
14
|
+
MULTITOOL_DIRECTORY = join_path_components(Path.home(), '.multitool')
|
|
15
|
+
|
|
16
|
+
MULTITOOL_LOG_FILE = join_path_components(MULTITOOL_DIRECTORY, f'{APP}.log')
|
|
17
|
+
|
|
18
|
+
MULTITOOL_PLUGINS_DIRECTORY = join_path_components(MULTITOOL_DIRECTORY, 'plugins')
|
|
19
|
+
MULTITOOL_PLUGINS_CONFIG_FILE = join_path_components(MULTITOOL_PLUGINS_DIRECTORY, 'config')
|
|
20
|
+
MULTITOOL_PLUGINS_PATH = join_path_components(MULTITOOL_PLUGINS_DIRECTORY, '__init__.py')
|
|
21
|
+
|
|
22
|
+
builtins.MULTITOOL_TOGGLE_SILENT = MULTITOOL_TOGGLE_SILENT
|
|
23
|
+
builtins.MULTITOOL_TOGGLE_VERBOSE = MULTITOOL_TOGGLE_VERBOSE
|
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# __main__.py
|
|
3
3
|
|
|
4
|
-
import codecs
|
|
5
|
-
import configparser
|
|
6
|
-
import importlib
|
|
7
|
-
import os
|
|
8
|
-
import re
|
|
9
|
-
import sys
|
|
10
|
-
import time
|
|
11
|
-
from functools import update_wrapper
|
|
12
|
-
|
|
13
4
|
import click
|
|
14
|
-
import requests
|
|
15
5
|
|
|
16
|
-
from multitool import
|
|
17
|
-
MULTITOOL_PLUGINS_PATH)
|
|
6
|
+
from multitool import APP, MULTITOOL_LOG_FILE, MULTITOOL_PLUGINS_DIRECTORY
|
|
18
7
|
from multitool import __version__ as version
|
|
19
8
|
from multitool.cls import AliasedGroup
|
|
20
|
-
from multitool.exceptions import
|
|
9
|
+
from multitool.exceptions import wrap_with_exception_handling
|
|
21
10
|
from multitool.plugins.commands import plugins
|
|
22
|
-
from multitool.utils import (
|
|
23
|
-
|
|
24
|
-
show_message)
|
|
11
|
+
from multitool.utils import (import_plugins_from_directory,
|
|
12
|
+
execute_function_on_directory_files, configure_global_logger)
|
|
25
13
|
|
|
26
14
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
27
15
|
|
|
@@ -45,15 +33,15 @@ def cli(ctx):
|
|
|
45
33
|
ctx.ensure_object(dict)
|
|
46
34
|
|
|
47
35
|
|
|
48
|
-
@
|
|
36
|
+
@wrap_with_exception_handling
|
|
49
37
|
def main():
|
|
50
|
-
|
|
38
|
+
configure_global_logger(MULTITOOL_LOG_FILE)
|
|
51
39
|
|
|
52
40
|
cli_commands = {plugins}
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
execute_function_on_directory_files(
|
|
55
43
|
MULTITOOL_PLUGINS_DIRECTORY,
|
|
56
|
-
|
|
44
|
+
import_plugins_from_directory,
|
|
57
45
|
args=(cli_commands,),
|
|
58
46
|
glob='[!.][!__]*/__init__.py',
|
|
59
47
|
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def echo(
|
|
6
|
+
*message,
|
|
7
|
+
exit_status=None,
|
|
8
|
+
make_silent=False,
|
|
9
|
+
current_verbosity=0,
|
|
10
|
+
expected_verbosity=0,
|
|
11
|
+
line_ending="\n",
|
|
12
|
+
should_flush=False
|
|
13
|
+
):
|
|
14
|
+
toggle_silent = builtins.MULTITOOL_TOGGLE_SILENT
|
|
15
|
+
toggle_verbose = builtins.MULTITOOL_TOGGLE_VERBOSE
|
|
16
|
+
if make_silent or toggle_silent:
|
|
17
|
+
if exit_status:
|
|
18
|
+
sys.exit(exit_status)
|
|
19
|
+
return
|
|
20
|
+
if current_verbosity or toggle_verbose >= expected_verbosity:
|
|
21
|
+
print(*message, end=line_ending, flush=should_flush)
|
|
22
|
+
if exit_status:
|
|
23
|
+
sys.exit(exit_status)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import inspect
|
|
3
|
-
import json
|
|
4
3
|
import logging
|
|
5
4
|
import sys
|
|
6
5
|
|
|
7
6
|
from multitool import console
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
def
|
|
9
|
+
def wrap_with_exception_handling(func):
|
|
11
10
|
@functools.wraps(func)
|
|
12
11
|
def wrapper(*args, **kwargs):
|
|
13
12
|
try:
|
|
File without changes
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import stat
|
|
5
|
+
import sys
|
|
6
|
+
from os import path
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from click_option_group import MutuallyExclusiveOptionGroup, optgroup
|
|
11
|
+
|
|
12
|
+
from multitool import (
|
|
13
|
+
APP,
|
|
14
|
+
MULTITOOL_PLUGINS_CONFIG_FILE,
|
|
15
|
+
MULTITOOL_PLUGINS_DIRECTORY,
|
|
16
|
+
MULTITOOL_PLUGINS_PATH,
|
|
17
|
+
console,
|
|
18
|
+
)
|
|
19
|
+
from multitool.silent import common_silent_options
|
|
20
|
+
from multitool.utils import (
|
|
21
|
+
is_directory,
|
|
22
|
+
is_regular_file,
|
|
23
|
+
create_directory,
|
|
24
|
+
read_file_content,
|
|
25
|
+
execute_function_on_directory_files,
|
|
26
|
+
create_empty_file,
|
|
27
|
+
)
|
|
28
|
+
from multitool.verbose import common_verbose_options
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---- Global Variables ----
|
|
32
|
+
plugins_command_help = 'Simple plugins manager for distributing commands.'
|
|
33
|
+
|
|
34
|
+
# ---- Git Check ----
|
|
35
|
+
is_git_installed = False
|
|
36
|
+
try:
|
|
37
|
+
from git import Repo
|
|
38
|
+
is_git_installed = True
|
|
39
|
+
except ImportError:
|
|
40
|
+
plugins_command_help = (
|
|
41
|
+
"Simple plugins manager for distributing commands. "
|
|
42
|
+
"[Warning: Git must be installed to use plugin commands]"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---- Helper Classes ----
|
|
47
|
+
class FileUtils:
|
|
48
|
+
"""Internal helper class for file/directory operations."""
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def chmod_directory(directory, mode):
|
|
52
|
+
"""Recursively change permissions of a directory and its contents."""
|
|
53
|
+
for root, dirs, files in os.walk(directory):
|
|
54
|
+
for d in dirs:
|
|
55
|
+
os.chmod(path.join(root, d), mode)
|
|
56
|
+
for f in files:
|
|
57
|
+
os.chmod(path.join(root, f), mode)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GitUtils:
|
|
61
|
+
"""Internal helper class for Git operations."""
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def exit_if_not_installed():
|
|
65
|
+
if not is_git_installed:
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PluginManager:
|
|
70
|
+
"""Internal helper class for MULTITOOL plugin management."""
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def initialize_plugins():
|
|
74
|
+
create_directory(MULTITOOL_PLUGINS_DIRECTORY)
|
|
75
|
+
create_empty_file(MULTITOOL_PLUGINS_PATH)
|
|
76
|
+
create_empty_file(MULTITOOL_PLUGINS_CONFIG_FILE)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def load_config(config_file=MULTITOOL_PLUGINS_CONFIG_FILE):
|
|
80
|
+
config = configparser.ConfigParser(allow_no_value=True)
|
|
81
|
+
config.read(config_file)
|
|
82
|
+
return config
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def clone_repositories(section="sources"):
|
|
86
|
+
PluginManager.initialize_plugins()
|
|
87
|
+
config = PluginManager.load_config()
|
|
88
|
+
if not config._sections:
|
|
89
|
+
return
|
|
90
|
+
sources = dict(config._sections[section])
|
|
91
|
+
for name, uri in sources.items():
|
|
92
|
+
dest = Path(MULTITOOL_PLUGINS_DIRECTORY) / name
|
|
93
|
+
if is_directory(dest):
|
|
94
|
+
continue
|
|
95
|
+
try:
|
|
96
|
+
console.echo(f"Installing {name}... ", line_ending="", should_flush=True)
|
|
97
|
+
Repo.clone_from(uri, dest)
|
|
98
|
+
console.echo("Done")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
console.echo(e)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def prune_unused_directories(section="sources"):
|
|
104
|
+
PluginManager.initialize_plugins()
|
|
105
|
+
config = PluginManager.load_config()
|
|
106
|
+
if not config._sections:
|
|
107
|
+
return
|
|
108
|
+
sources = dict(config._sections[section])
|
|
109
|
+
|
|
110
|
+
def _func(path_):
|
|
111
|
+
if not is_directory(path_):
|
|
112
|
+
return
|
|
113
|
+
name = Path(path_).stem
|
|
114
|
+
if name in sources:
|
|
115
|
+
return
|
|
116
|
+
console.echo(f"Removing {name}... ", line_ending="", should_flush=True)
|
|
117
|
+
plugin_directory = Path(MULTITOOL_PLUGINS_DIRECTORY) / name
|
|
118
|
+
try:
|
|
119
|
+
FileUtils.chmod_directory(str(Path(plugin_directory) / ".git"), stat.S_IRWXU)
|
|
120
|
+
shutil.rmtree(plugin_directory)
|
|
121
|
+
console.echo("Done")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
console.echo(e)
|
|
124
|
+
|
|
125
|
+
return execute_function_on_directory_files(
|
|
126
|
+
MULTITOOL_PLUGINS_DIRECTORY, _func, glob="[!.][!__]*"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def update_repositories():
|
|
131
|
+
def _func(path_):
|
|
132
|
+
if not is_directory(path_):
|
|
133
|
+
return
|
|
134
|
+
console.echo(f"Updating {Path(path_).stem}... ", line_ending="", should_flush=True)
|
|
135
|
+
repo = Repo(path_)
|
|
136
|
+
if repo.bare:
|
|
137
|
+
return
|
|
138
|
+
try:
|
|
139
|
+
repo.remotes["origin"].pull()
|
|
140
|
+
console.echo("Done")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
console.echo(e)
|
|
143
|
+
|
|
144
|
+
return execute_function_on_directory_files(
|
|
145
|
+
MULTITOOL_PLUGINS_DIRECTORY, _func, glob="[!.][!__]*"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def list_sources(section="sources"):
|
|
150
|
+
"""Return a dictionary of plugin sources from config."""
|
|
151
|
+
config = PluginManager.load_config()
|
|
152
|
+
return dict(config._sections.get(section, {}))
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def get_plugin_info(name):
|
|
156
|
+
"""Return the parsed plugin info JSON or None if file missing."""
|
|
157
|
+
info_file = Path(MULTITOOL_PLUGINS_DIRECTORY) / name / f"{APP}-info.json"
|
|
158
|
+
if not is_regular_file(info_file):
|
|
159
|
+
return None
|
|
160
|
+
return read_file_content(info_file, type="json")
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def print_latest_commit(name):
|
|
164
|
+
"""Print the latest Git commit of a plugin repository."""
|
|
165
|
+
GitUtils.exit_if_not_installed()
|
|
166
|
+
repo = Repo(Path(MULTITOOL_PLUGINS_DIRECTORY) / name)
|
|
167
|
+
console.echo(repo.git.log(
|
|
168
|
+
"--pretty=format:%Cred%h%Creset -%C(yellow)%d%Creset %s "
|
|
169
|
+
"%Cgreen(%cr) %C(bold blue)<%an>%Creset", "-1"
|
|
170
|
+
))
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def print_dependencies(plugins_info):
|
|
174
|
+
"""Print the 'Requires' field from plugin info, if any."""
|
|
175
|
+
requires = plugins_info.get("Requires")
|
|
176
|
+
if requires:
|
|
177
|
+
console.echo(requires)
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def print_full_info(plugins_info):
|
|
181
|
+
"""Print all key/value pairs from plugin info."""
|
|
182
|
+
for key, value in plugins_info.items():
|
|
183
|
+
console.echo(f"{key}: {value}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ---- CLI Commands ----
|
|
187
|
+
@click.group(help=plugins_command_help)
|
|
188
|
+
def plugins():
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@plugins.command(help="Edit config file manually.")
|
|
193
|
+
@common_silent_options
|
|
194
|
+
@common_verbose_options
|
|
195
|
+
@click.option(
|
|
196
|
+
"-a/-A",
|
|
197
|
+
"--apply-changes/--no-apply-changes",
|
|
198
|
+
default=False,
|
|
199
|
+
help="Install plugins from new sources after exiting the editor.",
|
|
200
|
+
show_default=True,
|
|
201
|
+
)
|
|
202
|
+
def configure(silent, verbose, apply_changes):
|
|
203
|
+
GitUtils.exit_if_not_installed()
|
|
204
|
+
PluginManager.initialize_plugins()
|
|
205
|
+
click.edit(filename=MULTITOOL_PLUGINS_CONFIG_FILE)
|
|
206
|
+
if apply_changes:
|
|
207
|
+
PluginManager.clone_repositories()
|
|
208
|
+
PluginManager.prune_unused_directories()
|
|
209
|
+
else:
|
|
210
|
+
console.echo(f"\n Run `{APP} plugins update` to apply any changes,")
|
|
211
|
+
console.echo(f" or rerun `{APP} plugins configure` with `-a`")
|
|
212
|
+
console.echo(" to apply changes automatically.\n")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@plugins.command(help="Update or install plugins.")
|
|
216
|
+
@common_silent_options
|
|
217
|
+
@common_verbose_options
|
|
218
|
+
def update(silent, verbose, section="sources"):
|
|
219
|
+
GitUtils.exit_if_not_installed()
|
|
220
|
+
PluginManager.clone_repositories()
|
|
221
|
+
PluginManager.update_repositories()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@plugins.command(help="Show plugins information.")
|
|
225
|
+
@common_silent_options
|
|
226
|
+
@common_verbose_options
|
|
227
|
+
@click.option("-n", "--name", help="Name of the plugins package")
|
|
228
|
+
@optgroup.group("Filter options", cls=MutuallyExclusiveOptionGroup, help="The filter options")
|
|
229
|
+
@optgroup.option(
|
|
230
|
+
"--show-commit-only/--no-show-commit-only",
|
|
231
|
+
default=False,
|
|
232
|
+
help="Only print latest Git commit log",
|
|
233
|
+
)
|
|
234
|
+
@optgroup.option(
|
|
235
|
+
"--show-dependencies-only/--no-show-dependencies-only",
|
|
236
|
+
default=False,
|
|
237
|
+
help="Only print list of required packages",
|
|
238
|
+
)
|
|
239
|
+
def show(silent, verbose, name, section="sources", show_commit_only=False, show_dependencies_only=False):
|
|
240
|
+
"""Show plugins sources, info, commits, or dependencies based on options."""
|
|
241
|
+
|
|
242
|
+
if not name:
|
|
243
|
+
# List all plugin sources
|
|
244
|
+
sources = PluginManager.list_sources(section)
|
|
245
|
+
for plugin_name, uri in sources.items():
|
|
246
|
+
console.echo(f"{plugin_name}: {uri}")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
plugins_info = PluginManager.get_plugin_info(name)
|
|
250
|
+
if not plugins_info:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
if show_commit_only:
|
|
254
|
+
PluginManager.print_latest_commit(name)
|
|
255
|
+
elif show_dependencies_only:
|
|
256
|
+
PluginManager.print_dependencies(plugins_info)
|
|
257
|
+
else:
|
|
258
|
+
PluginManager.print_full_info(plugins_info)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@plugins.command(help="Prune plugins with removed sources.")
|
|
262
|
+
@common_silent_options
|
|
263
|
+
@common_verbose_options
|
|
264
|
+
def prune(silent, verbose, section="sources"):
|
|
265
|
+
GitUtils.exit_if_not_installed()
|
|
266
|
+
PluginManager.prune_unused_directories()
|
|
File without changes
|
|
@@ -10,38 +10,76 @@ from pathlib import Path
|
|
|
10
10
|
import click
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def
|
|
14
|
-
if
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return
|
|
13
|
+
def apply_function_on_iterable(iterable, func, state_op="append", args=(), kwargs=None):
|
|
14
|
+
if kwargs is None:
|
|
15
|
+
kwargs = {}
|
|
16
|
+
state = []
|
|
17
|
+
for item in iterable:
|
|
18
|
+
_tuple = (item,)
|
|
19
|
+
result = func(*(_tuple + args), **kwargs)
|
|
20
|
+
if result:
|
|
21
|
+
getattr(state, state_op)(result)
|
|
22
|
+
return state
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def
|
|
26
|
-
|
|
25
|
+
def configure_global_logger(log_file):
|
|
26
|
+
create_empty_file(log_file)
|
|
27
|
+
remove_file_if_above_size(log_file, size_kb=1000)
|
|
28
|
+
logging.basicConfig(
|
|
29
|
+
filename=log_file,
|
|
30
|
+
level=logging.WARNING,
|
|
31
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
32
|
+
)
|
|
27
33
|
|
|
28
34
|
|
|
29
|
-
def
|
|
30
|
-
|
|
35
|
+
def create_empty_file(path):
|
|
36
|
+
try:
|
|
37
|
+
create_directory(os.path.split(path)[0])
|
|
38
|
+
if not os.path.exists(path):
|
|
39
|
+
with open(path, "x"):
|
|
40
|
+
os.utime(path, None)
|
|
41
|
+
except FileExistsError:
|
|
42
|
+
logging.warning(f"{inspect.stack()[0][3]}; will ignore FileExistsError")
|
|
31
43
|
|
|
32
44
|
|
|
33
|
-
def
|
|
34
|
-
|
|
45
|
+
def create_directory(path):
|
|
46
|
+
if not path:
|
|
47
|
+
return
|
|
48
|
+
if not os.path.exists(path):
|
|
49
|
+
try:
|
|
50
|
+
os.makedirs(path)
|
|
51
|
+
except FileExistsError:
|
|
52
|
+
logging.warning(f"{inspect.stack()[0][3]}; will ignore FileExistsError")
|
|
53
|
+
|
|
35
54
|
|
|
55
|
+
def execute_function_on_directory_files(dir, func, glob="**/*", args=(), kwargs=None):
|
|
56
|
+
if kwargs is None:
|
|
57
|
+
kwargs = {}
|
|
58
|
+
state = []
|
|
59
|
+
for file_path in Path(get_resolved_directory_path(dir)).resolve().glob(glob):
|
|
60
|
+
_tuple = (str(file_path),)
|
|
61
|
+
result = func(*(_tuple + args), **kwargs)
|
|
62
|
+
if result:
|
|
63
|
+
state.append(result)
|
|
64
|
+
return state
|
|
36
65
|
|
|
37
|
-
|
|
66
|
+
|
|
67
|
+
def get_resolved_directory_path(target_directory=None):
|
|
68
|
+
if target_directory:
|
|
69
|
+
if not os.path.exists(target_directory):
|
|
70
|
+
os.makedirs(target_directory)
|
|
71
|
+
return str(Path(target_directory).resolve())
|
|
72
|
+
return os.getcwd()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def import_plugins_from_directory(plugins_init_file, existing_commands):
|
|
38
76
|
try:
|
|
39
77
|
spec = importlib.util.spec_from_file_location('plugins_modules', plugins_init_file)
|
|
40
78
|
module = importlib.util.module_from_spec(spec)
|
|
41
79
|
sys.modules[spec.name] = module
|
|
42
80
|
spec.loader.exec_module(module)
|
|
43
|
-
import plugins_modules
|
|
44
|
-
from plugins_modules import __all__ as all_plugins_modules
|
|
81
|
+
import plugins_modules # type: ignore
|
|
82
|
+
from plugins_modules import __all__ as all_plugins_modules # type: ignore
|
|
45
83
|
|
|
46
84
|
for module in all_plugins_modules:
|
|
47
85
|
_module = getattr(plugins_modules, module)
|
|
@@ -53,73 +91,39 @@ def import_all_modules_in_directory(plugins_init_file, existing_commands):
|
|
|
53
91
|
)
|
|
54
92
|
|
|
55
93
|
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
os.makedirs(path)
|
|
60
|
-
except FileExistsError:
|
|
61
|
-
logging.warning(f'{inspect.stack()[0][3]}; will ignore FileExistsError')
|
|
94
|
+
def is_directory(d):
|
|
95
|
+
return os.path.isdir(d)
|
|
62
96
|
|
|
63
97
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
if type == 'json':
|
|
67
|
-
return json.loads(f.read())
|
|
68
|
-
return f.read()
|
|
98
|
+
def is_regular_file(f):
|
|
99
|
+
return os.path.isfile(f)
|
|
69
100
|
|
|
70
101
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
os.remove(file)
|
|
102
|
+
def is_truthy_envvar(value):
|
|
103
|
+
return value in (True, "True", "true", "1")
|
|
74
104
|
|
|
75
105
|
|
|
76
|
-
def
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
for file_path in Path(resolve_target_directory(dir)).resolve().glob(glob):
|
|
87
|
-
_tuple = (str(file_path),)
|
|
88
|
-
result = func(*(_tuple + args), **kwargs)
|
|
89
|
-
if result:
|
|
90
|
-
state.append(result)
|
|
91
|
-
return state
|
|
106
|
+
def join_path_components(*components):
|
|
107
|
+
if not components:
|
|
108
|
+
return
|
|
109
|
+
path = None
|
|
110
|
+
for component in components:
|
|
111
|
+
if not path:
|
|
112
|
+
path = Path(component)
|
|
113
|
+
else:
|
|
114
|
+
path /= component
|
|
115
|
+
return str(path)
|
|
92
116
|
|
|
93
117
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
_tuple = (item,)
|
|
98
|
-
result = func(*(_tuple + args), **kwargs)
|
|
99
|
-
if result:
|
|
100
|
-
getattr(state, state_op)(result)
|
|
101
|
-
return state
|
|
118
|
+
def read_file_content(file, type="text"):
|
|
119
|
+
with open(file, "r") as f:
|
|
120
|
+
return json.loads(f.read()) if type == "json" else f.read()
|
|
102
121
|
|
|
103
122
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
filename=log_file,
|
|
108
|
-
level=logging.DEBUG,
|
|
109
|
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
110
|
-
)
|
|
111
|
-
remove_file_above_size(log_file, size_kb=1000)
|
|
123
|
+
def remove_file_if_above_size(file, size_kb=100):
|
|
124
|
+
if os.path.getsize(file) > size_kb * 1024:
|
|
125
|
+
os.remove(file)
|
|
112
126
|
|
|
113
127
|
|
|
114
128
|
def show_message(msg):
|
|
115
129
|
print(msg)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def touch(path):
|
|
119
|
-
try:
|
|
120
|
-
make_dirs(os.path.split(path)[0])
|
|
121
|
-
if not os.path.exists(path):
|
|
122
|
-
with open(path, 'x'):
|
|
123
|
-
os.utime(path, None)
|
|
124
|
-
except FileExistsError:
|
|
125
|
-
logging.warning(f'{inspect.stack()[0][3]}; will ignore FileExistsError')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: multitool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: General-purpose command-line interface with plugins support
|
|
5
5
|
Home-page: https://github.com/mdelotavo/multitool
|
|
6
6
|
Author: Matthew Delotavo
|
|
@@ -13,7 +13,7 @@ Classifier: Intended Audience :: Developers
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
|
|
19
19
|
multitool
|
|
@@ -25,9 +25,7 @@ Multitool is a general-purpose command-line interface with plugins support.
|
|
|
25
25
|
Why does this exist?
|
|
26
26
|
--------------------
|
|
27
27
|
|
|
28
|
-
I built this tool so
|
|
29
|
-
|
|
30
|
-
The plugins manager uses ``git`` to manage plugins installed from remote ``git`` repositories.
|
|
28
|
+
I built this tool so I can quickly create and distribute command-line tools for consulting work and personal use.
|
|
31
29
|
|
|
32
30
|
-----
|
|
33
31
|
Usage
|
|
@@ -49,9 +47,6 @@ Usage
|
|
|
49
47
|
Commands:
|
|
50
48
|
plugins Simple plugins manager for distributing commands.
|
|
51
49
|
|
|
52
|
-
.. ..
|
|
53
|
-
The plugins features are based off those found in the `apigeecli`_.
|
|
54
|
-
|
|
55
50
|
----------------
|
|
56
51
|
Managing plugins
|
|
57
52
|
----------------
|
|
@@ -123,7 +118,7 @@ This will install and update plugins.
|
|
|
123
118
|
Pruning
|
|
124
119
|
^^^^^^^
|
|
125
120
|
|
|
126
|
-
If you specified the ``-a`` option when running ``multitool plugins configure`` then the removal of plugins will occur automatically.
|
|
121
|
+
If you remove plugins from the config file or comment them out, and you then specified the ``-a`` option when running ``multitool plugins configure`` then the removal of plugins will occur automatically.
|
|
127
122
|
Otherwise you can run::
|
|
128
123
|
|
|
129
124
|
multitool plugins prune
|
|
@@ -147,6 +142,17 @@ In order for this to work, the plugin needs to have the ``Requires`` key in the
|
|
|
147
142
|
|
|
148
143
|
pip3 install $(multitool plugins show -n PLUGIN_NAME --show-dependencies-only)
|
|
149
144
|
|
|
145
|
+
^^^^^^^^^^^^^^^
|
|
146
|
+
Troubleshooting
|
|
147
|
+
^^^^^^^^^^^^^^^
|
|
148
|
+
|
|
149
|
+
If you encounter issues with plugins or commands, you can find more detailed debug information and error messages in the log file:
|
|
150
|
+
::
|
|
151
|
+
|
|
152
|
+
~/.multitool/multitool.log
|
|
153
|
+
|
|
154
|
+
Reviewing this file can help you diagnose installation problems, missing dependencies, or Git-related errors.
|
|
155
|
+
|
|
150
156
|
.. ..
|
|
151
157
|
----------------------------
|
|
152
158
|
Create a virtual environment
|
|
@@ -168,7 +174,6 @@ In order for this to work, the plugin needs to have the ``Requires`` key in the
|
|
|
168
174
|
deactivate
|
|
169
175
|
|
|
170
176
|
.. _`click`: https://click.palletsprojects.com/
|
|
171
|
-
.. _`apigeecli`: https://pypi.org/project/apigeecli/
|
|
172
177
|
.. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
|
|
173
178
|
.. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
|
|
174
179
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import os
|
|
1
|
+
import os
|
|
2
2
|
|
|
3
3
|
from multitool import APP
|
|
4
4
|
from multitool import __version__ as version
|
|
@@ -25,27 +25,18 @@ SETUP_ARGS = dict(
|
|
|
25
25
|
'Operating System :: OS Independent',
|
|
26
26
|
'Programming Language :: Python :: 3.7',
|
|
27
27
|
],
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
entry_points={'console_scripts': ['multitool=multitool.__main__:main']},
|
|
28
|
+
entry_points = {
|
|
29
|
+
'console_scripts': [
|
|
30
|
+
'multitool=multitool.__main__:main'
|
|
31
|
+
]
|
|
32
|
+
},
|
|
34
33
|
install_requires = [
|
|
35
|
-
'
|
|
36
|
-
'click',
|
|
37
|
-
'click-
|
|
38
|
-
'
|
|
39
|
-
'colorama',
|
|
40
|
-
'pyotp',
|
|
41
|
-
'requests',
|
|
42
|
-
'tqdm',
|
|
43
|
-
'tabulate',
|
|
44
|
-
'pyjwt',
|
|
45
|
-
'python-gnupg>=0.3.5',
|
|
46
|
-
'gitpython'
|
|
34
|
+
'click>=8.1.3',
|
|
35
|
+
'click-aliases>=1.0.1',
|
|
36
|
+
'click-option-group>=0.5.5',
|
|
37
|
+
'gitpython>=3.1.30'
|
|
47
38
|
],
|
|
48
|
-
python_requires=">=3.
|
|
39
|
+
python_requires=">=3.7",
|
|
49
40
|
)
|
|
50
41
|
|
|
51
42
|
if __name__ == '__main__':
|
|
File without changes
|
|
File without changes
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
APP = 'multitool'
|
|
2
|
-
__version__ = '0.3.2'
|
|
3
|
-
description = 'General-purpose command-line interface with plugins support'
|
|
4
|
-
# long_description = 'General-purpose command-line interface with plugins support'
|
|
5
|
-
|
|
6
|
-
import builtins
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from multitool.utils import gen_path_str
|
|
10
|
-
|
|
11
|
-
MULTITOOL_TOGGLE_SILENT = False
|
|
12
|
-
MULTITOOL_TOGGLE_VERBOSE = 0
|
|
13
|
-
|
|
14
|
-
MULTITOOL_DIRECTORY = gen_path_str(Path.home(), '.multitool')
|
|
15
|
-
|
|
16
|
-
MULTITOOL_LOG_FILE = gen_path_str(MULTITOOL_DIRECTORY, f'{APP}.log')
|
|
17
|
-
|
|
18
|
-
MULTITOOL_PLUGINS_DIRECTORY = gen_path_str(MULTITOOL_DIRECTORY, 'plugins')
|
|
19
|
-
MULTITOOL_PLUGINS_CONFIG_FILE = gen_path_str(MULTITOOL_PLUGINS_DIRECTORY, 'config')
|
|
20
|
-
MULTITOOL_PLUGINS_PATH = gen_path_str(MULTITOOL_PLUGINS_DIRECTORY, '__init__.py')
|
|
21
|
-
|
|
22
|
-
builtins.MULTITOOL_TOGGLE_SILENT = MULTITOOL_TOGGLE_SILENT
|
|
23
|
-
builtins.MULTITOOL_TOGGLE_VERBOSE = MULTITOOL_TOGGLE_VERBOSE
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import builtins
|
|
2
|
-
import sys
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def echo(
|
|
6
|
-
*message, status=None, silent=False, curr_verbosity=0, expc_verbosity=0, end='\n', flush=False
|
|
7
|
-
):
|
|
8
|
-
toggle_silent = builtins.MULTITOOL_TOGGLE_SILENT
|
|
9
|
-
toggle_verbose = builtins.MULTITOOL_TOGGLE_VERBOSE
|
|
10
|
-
if silent or toggle_silent:
|
|
11
|
-
if status:
|
|
12
|
-
sys.exit(status)
|
|
13
|
-
return
|
|
14
|
-
if curr_verbosity or toggle_verbose >= expc_verbosity:
|
|
15
|
-
print(*message, end=end, flush=flush)
|
|
16
|
-
if status:
|
|
17
|
-
sys.exit(status)
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import configparser
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import shutil
|
|
5
|
-
import stat
|
|
6
|
-
import sys
|
|
7
|
-
from os import path
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
import click
|
|
11
|
-
from click_option_group import MutuallyExclusiveOptionGroup, optgroup
|
|
12
|
-
|
|
13
|
-
from multitool import (MULTITOOL_PLUGINS_CONFIG_FILE,
|
|
14
|
-
MULTITOOL_PLUGINS_DIRECTORY, MULTITOOL_PLUGINS_PATH,
|
|
15
|
-
console)
|
|
16
|
-
from multitool.silent import common_silent_options
|
|
17
|
-
from multitool.utils import (is_dir, is_file, make_dirs, read_file,
|
|
18
|
-
run_func_on_dir_files, touch)
|
|
19
|
-
from multitool.verbose import common_verbose_options
|
|
20
|
-
|
|
21
|
-
is_git_installed = False
|
|
22
|
-
plugins_command_help = 'Simple plugins manager for distributing commands.'
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
import git
|
|
26
|
-
from git import Git, Repo
|
|
27
|
-
|
|
28
|
-
is_git_installed = True
|
|
29
|
-
except ImportError:
|
|
30
|
-
plugins_command_help = '[Git not found! Please install Git first.] Simple plugins manager for distributing commands. This command requires Git to be installed.'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def exit_if_git_not_installed():
|
|
34
|
-
if not is_git_installed:
|
|
35
|
-
sys.exit(0)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@click.group(help=plugins_command_help)
|
|
39
|
-
def plugins():
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def init():
|
|
44
|
-
make_dirs(MULTITOOL_PLUGINS_DIRECTORY)
|
|
45
|
-
touch(MULTITOOL_PLUGINS_PATH)
|
|
46
|
-
touch(MULTITOOL_PLUGINS_CONFIG_FILE)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def load_config(config_file=MULTITOOL_PLUGINS_CONFIG_FILE):
|
|
50
|
-
config = configparser.ConfigParser(allow_no_value=True)
|
|
51
|
-
config.read(config_file)
|
|
52
|
-
return config
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def save_config(plugins_file, config_file, section='sources'):
|
|
56
|
-
init()
|
|
57
|
-
config = load_config()
|
|
58
|
-
if not config._sections:
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def clone_all(section='sources'):
|
|
63
|
-
init()
|
|
64
|
-
config = load_config()
|
|
65
|
-
if not config._sections:
|
|
66
|
-
return
|
|
67
|
-
sources = dict(config._sections[section])
|
|
68
|
-
for name, uri in sources.items():
|
|
69
|
-
dest = Path(MULTITOOL_PLUGINS_DIRECTORY) / name
|
|
70
|
-
if is_dir(dest):
|
|
71
|
-
continue
|
|
72
|
-
try:
|
|
73
|
-
console.echo(f'Installing {name}... ', end='', flush=True)
|
|
74
|
-
Repo.clone_from(uri, dest)
|
|
75
|
-
console.echo('Done')
|
|
76
|
-
except Exception as e:
|
|
77
|
-
console.echo(e)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def pull_all():
|
|
81
|
-
def _func(path):
|
|
82
|
-
if not is_dir(path):
|
|
83
|
-
return
|
|
84
|
-
console.echo(f'Updating {Path(path).stem}... ', end='', flush=True)
|
|
85
|
-
repo = Repo(path)
|
|
86
|
-
if repo.bare:
|
|
87
|
-
return
|
|
88
|
-
try:
|
|
89
|
-
repo.remotes['origin'].pull()
|
|
90
|
-
console.echo('Done')
|
|
91
|
-
except Exception as e:
|
|
92
|
-
console.echo(e)
|
|
93
|
-
|
|
94
|
-
return run_func_on_dir_files(MULTITOOL_PLUGINS_DIRECTORY, _func, glob='[!.][!__]*')
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@plugins.command(help='Edit config file manually.')
|
|
98
|
-
@common_silent_options
|
|
99
|
-
@common_verbose_options
|
|
100
|
-
@click.option(
|
|
101
|
-
'-a/-A',
|
|
102
|
-
'--apply-changes/--no-apply-changes',
|
|
103
|
-
default=False,
|
|
104
|
-
help='Install plugins from new sources after exiting the editor.',
|
|
105
|
-
show_default=True,
|
|
106
|
-
)
|
|
107
|
-
def configure(silent, verbose, apply_changes):
|
|
108
|
-
exit_if_git_not_installed()
|
|
109
|
-
init()
|
|
110
|
-
click.edit(filename=MULTITOOL_PLUGINS_CONFIG_FILE)
|
|
111
|
-
if apply_changes:
|
|
112
|
-
clone_all()
|
|
113
|
-
prune_all()
|
|
114
|
-
else:
|
|
115
|
-
console.echo('\n Run `multitool plugins update` to apply any changes,')
|
|
116
|
-
console.echo(' or rerun `multitool plugins configure` with `-a`')
|
|
117
|
-
console.echo(' to apply changes automatically.\n')
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def install():
|
|
121
|
-
pass
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@plugins.command(help='Update or install plugins.')
|
|
125
|
-
@common_silent_options
|
|
126
|
-
@common_verbose_options
|
|
127
|
-
def update(silent, verbose, section='sources'):
|
|
128
|
-
exit_if_git_not_installed()
|
|
129
|
-
clone_all()
|
|
130
|
-
pull_all()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@plugins.command(help='Show plugins information.')
|
|
134
|
-
@common_silent_options
|
|
135
|
-
@common_verbose_options
|
|
136
|
-
@click.option('-n', '--name', help='name of the plugins package')
|
|
137
|
-
@optgroup.group('Filter options', cls=MutuallyExclusiveOptionGroup, help='The filter options')
|
|
138
|
-
@optgroup.option(
|
|
139
|
-
'--show-commit-only/--no-show-commit-only',
|
|
140
|
-
default=False,
|
|
141
|
-
help='only print latest Git commit log',
|
|
142
|
-
)
|
|
143
|
-
@optgroup.option(
|
|
144
|
-
'--show-dependencies-only/--no-show-dependencies-only',
|
|
145
|
-
default=False,
|
|
146
|
-
help='only print list of required packages',
|
|
147
|
-
)
|
|
148
|
-
def show(
|
|
149
|
-
silent, verbose, name, section='sources', show_commit_only=False, show_dependencies_only=False
|
|
150
|
-
):
|
|
151
|
-
if not name:
|
|
152
|
-
config = load_config()
|
|
153
|
-
if not config._sections:
|
|
154
|
-
return
|
|
155
|
-
sources = dict(config._sections[section])
|
|
156
|
-
for name, uri in sources.items():
|
|
157
|
-
console.echo(f'{name}: {uri}')
|
|
158
|
-
return
|
|
159
|
-
plugins_info_file = Path(MULTITOOL_PLUGINS_DIRECTORY) / name / 'multitool-info.json'
|
|
160
|
-
if not is_file(plugins_info_file):
|
|
161
|
-
return
|
|
162
|
-
plugins_info = read_file(plugins_info_file, type='json')
|
|
163
|
-
if show_commit_only:
|
|
164
|
-
exit_if_git_not_installed()
|
|
165
|
-
console.echo(
|
|
166
|
-
Repo(Path(MULTITOOL_PLUGINS_DIRECTORY) / name).git.log(
|
|
167
|
-
'--pretty=format:%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset',
|
|
168
|
-
'-1',
|
|
169
|
-
)
|
|
170
|
-
)
|
|
171
|
-
return
|
|
172
|
-
if show_dependencies_only:
|
|
173
|
-
if plugins_info.get('Requires'):
|
|
174
|
-
console.echo(plugins_info.get('Requires'))
|
|
175
|
-
return
|
|
176
|
-
for k, v in plugins_info.items():
|
|
177
|
-
console.echo(f'{k}: {v}')
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def info():
|
|
181
|
-
pass
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def chmod_directory(directory, mode):
|
|
185
|
-
"""https://stackoverflow.com/a/58878271"""
|
|
186
|
-
for root, dirs, files in os.walk(directory):
|
|
187
|
-
for dir in dirs:
|
|
188
|
-
os.chmod(path.join(root, dir), mode)
|
|
189
|
-
for file in files:
|
|
190
|
-
os.chmod(path.join(root, file), mode)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def prune_all(section='sources'):
|
|
194
|
-
init()
|
|
195
|
-
config = load_config()
|
|
196
|
-
if not config._sections:
|
|
197
|
-
return
|
|
198
|
-
sources = dict(config._sections[section])
|
|
199
|
-
|
|
200
|
-
def _func(path):
|
|
201
|
-
if not is_dir(path):
|
|
202
|
-
return
|
|
203
|
-
name = Path(path).stem
|
|
204
|
-
if name in sources.keys():
|
|
205
|
-
return
|
|
206
|
-
console.echo(f'Removing {name}... ', end='', flush=True)
|
|
207
|
-
plugin_directory = Path(MULTITOOL_PLUGINS_DIRECTORY) / name
|
|
208
|
-
try:
|
|
209
|
-
chmod_directory(str(Path(plugin_directory) / '.git'), stat.S_IRWXU)
|
|
210
|
-
shutil.rmtree(plugin_directory)
|
|
211
|
-
console.echo('Done')
|
|
212
|
-
except Exception as e:
|
|
213
|
-
console.echo(e)
|
|
214
|
-
|
|
215
|
-
return run_func_on_dir_files(MULTITOOL_PLUGINS_DIRECTORY, _func, glob='[!.][!__]*')
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
@plugins.command(help='Prune plugins with removed sources.')
|
|
219
|
-
@common_silent_options
|
|
220
|
-
@common_verbose_options
|
|
221
|
-
def prune(silent, verbose, section='sources'):
|
|
222
|
-
exit_if_git_not_installed()
|
|
223
|
-
prune_all()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def uninstall():
|
|
227
|
-
pass
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def clean():
|
|
231
|
-
pass
|