multitool 0.5.0__tar.gz → 0.6.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.5.0 → multitool-0.6.0}/PKG-INFO +17 -23
  2. {multitool-0.5.0 → multitool-0.6.0}/README.rst +3 -12
  3. {multitool-0.5.0 → multitool-0.6.0}/multitool/__init__.py +3 -3
  4. {multitool-0.5.0 → multitool-0.6.0}/multitool/__main__.py +7 -19
  5. {multitool-0.5.0 → multitool-0.6.0}/multitool/cls.py +1 -0
  6. multitool-0.6.0/multitool/console.py +25 -0
  7. {multitool-0.5.0 → multitool-0.6.0}/multitool/exceptions.py +1 -0
  8. multitool-0.6.0/multitool/plugins/commands.py +224 -0
  9. {multitool-0.5.0 → multitool-0.6.0}/multitool/silent.py +5 -5
  10. {multitool-0.5.0 → multitool-0.6.0}/multitool/utils.py +15 -16
  11. {multitool-0.5.0 → multitool-0.6.0}/multitool/verbose.py +5 -5
  12. {multitool-0.5.0 → multitool-0.6.0}/multitool.egg-info/PKG-INFO +17 -23
  13. {multitool-0.5.0 → multitool-0.6.0}/multitool.egg-info/SOURCES.txt +1 -1
  14. {multitool-0.5.0 → multitool-0.6.0}/multitool.egg-info/entry_points.txt +0 -1
  15. {multitool-0.5.0 → multitool-0.6.0}/multitool.egg-info/requires.txt +2 -2
  16. {multitool-0.5.0 → multitool-0.6.0}/multitool.egg-info/top_level.txt +0 -1
  17. multitool-0.6.0/pyproject.toml +45 -0
  18. {multitool-0.5.0 → multitool-0.6.0}/tests/test_utils.py +1 -0
  19. multitool-0.5.0/multitool/console.py +0 -23
  20. multitool-0.5.0/multitool/plugins/commands.py +0 -266
  21. multitool-0.5.0/setup.py +0 -46
  22. {multitool-0.5.0 → multitool-0.6.0}/LICENSE +0 -0
  23. {multitool-0.5.0 → multitool-0.6.0}/multitool/plugins/__init__.py +0 -0
  24. {multitool-0.5.0 → multitool-0.6.0}/multitool.egg-info/dependency_links.txt +0 -0
  25. {multitool-0.5.0 → multitool-0.6.0}/setup.cfg +0 -0
  26. {multitool-0.5.0 → multitool-0.6.0}/tests/__init__.py +0 -0
@@ -1,31 +1,30 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: multitool
3
- Version: 0.5.0
4
- Summary: General-purpose command-line interface with plugins support
5
- Home-page: https://github.com/mdelotavo/multitool
6
- Author: Matthew Delotavo
7
- Author-email: matthew.t.delotavo@gmail.com
8
- License: MIT
9
- Platform: UNKNOWN
3
+ Version: 0.6.0
4
+ Summary: Quickly create and distribute 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
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Environment :: Console
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Operating System :: OS Independent
15
14
  Classifier: Programming Language :: Python :: 3.7
16
15
  Requires-Python: >=3.7
16
+ Description-Content-Type: text/x-rst
17
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
18
23
 
19
24
  multitool
20
25
  =========
21
26
 
22
- Multitool is a general-purpose command-line interface with plugins support.
23
-
24
- --------------------
25
- Why does this exist?
26
- --------------------
27
-
28
- I built this tool so I can quickly create and distribute command-line tools for consulting work and personal use.
27
+ Quickly create and distribute command-line tools with plugins.
29
28
 
30
29
  -----
31
30
  Usage
@@ -35,17 +34,14 @@ Usage
35
34
 
36
35
  Usage: multitool [OPTIONS] COMMAND [ARGS]...
37
36
 
38
- Welcome to the Multitool command-line interface!
39
-
40
- PyPI: https://pypi.org/project/multitool/
41
- GitHub: https://github.com/mdelotavo/multitool
37
+ Quickly create and distribute command-line tools.
42
38
 
43
39
  Options:
44
40
  -V, --version Show the version and exit.
45
41
  -h, --help Show this message and exit.
46
42
 
47
43
  Commands:
48
- plugins Simple plugins manager for distributing commands.
44
+ plugins Plugins manager for distributing commands.
49
45
 
50
46
  ----------------
51
47
  Managing plugins
@@ -176,5 +172,3 @@ Reviewing this file can help you diagnose installation problems, missing depende
176
172
  .. _`click`: https://click.palletsprojects.com/
177
173
  .. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
178
174
  .. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
179
-
180
-
@@ -1,13 +1,7 @@
1
1
  multitool
2
2
  =========
3
3
 
4
- Multitool is a general-purpose command-line interface with plugins support.
5
-
6
- --------------------
7
- Why does this exist?
8
- --------------------
9
-
10
- I built this tool so I can quickly create and distribute command-line tools for consulting work and personal use.
4
+ Quickly create and distribute command-line tools with plugins.
11
5
 
12
6
  -----
13
7
  Usage
@@ -17,17 +11,14 @@ Usage
17
11
 
18
12
  Usage: multitool [OPTIONS] COMMAND [ARGS]...
19
13
 
20
- Welcome to the Multitool command-line interface!
21
-
22
- PyPI: https://pypi.org/project/multitool/
23
- GitHub: https://github.com/mdelotavo/multitool
14
+ Quickly create and distribute command-line tools.
24
15
 
25
16
  Options:
26
17
  -V, --version Show the version and exit.
27
18
  -h, --help Show this message and exit.
28
19
 
29
20
  Commands:
30
- plugins Simple plugins manager for distributing commands.
21
+ plugins Plugins manager for distributing commands.
31
22
 
32
23
  ----------------
33
24
  Managing plugins
@@ -1,7 +1,7 @@
1
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'
2
+ __version__ = "0.6.0"
3
+ description = 'Quickly create and distribute command-line tools.'
4
+ long_description = 'Quickly create and distribute command-line tools with plugins.'
5
5
 
6
6
  import builtins
7
7
  from pathlib import Path
@@ -8,28 +8,16 @@ 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 (import_plugins_from_directory,
12
- execute_function_on_directory_files, configure_global_logger)
11
+ from multitool.utils import (configure_global_logger, execute_function_on_directory_files, import_plugins_from_directory)
13
12
 
14
13
  CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
15
14
 
16
15
 
17
- @click.group(
18
- context_settings=CONTEXT_SETTINGS, cls=AliasedGroup, invoke_without_command=False, chain=False
19
- )
16
+ @click.group(context_settings=CONTEXT_SETTINGS, cls=AliasedGroup, invoke_without_command=False, chain=False)
20
17
  @click.version_option(version, '-V', '--version')
21
18
  @click.pass_context
22
19
  def cli(ctx):
23
- """Welcome to the Multitool command-line interface!
24
-
25
- \b
26
- PyPI: https://pypi.org/project/multitool/
27
- GitHub: https://github.com/mdelotavo/multitool
28
-
29
- \f
30
-
31
- :param click.core.Context ctx: Click context.
32
- """
20
+ """Quickly create and distribute command-line tools."""
33
21
  ctx.ensure_object(dict)
34
22
 
35
23
 
@@ -40,10 +28,10 @@ def main():
40
28
  cli_commands = {plugins}
41
29
 
42
30
  execute_function_on_directory_files(
43
- MULTITOOL_PLUGINS_DIRECTORY,
44
- import_plugins_from_directory,
45
- args=(cli_commands,),
46
- glob='[!.][!__]*/__init__.py',
31
+ MULTITOOL_PLUGINS_DIRECTORY,
32
+ import_plugins_from_directory,
33
+ args=(cli_commands, ),
34
+ glob='[!.][!__]*/__init__.py',
47
35
  )
48
36
 
49
37
  for command in cli_commands:
@@ -2,6 +2,7 @@ import click
2
2
 
3
3
 
4
4
  class AliasedGroup(click.Group):
5
+
5
6
  def get_command(self, ctx, cmd_name):
6
7
  rv = click.Group.get_command(self, ctx, cmd_name)
7
8
  if rv is not None:
@@ -0,0 +1,25 @@
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
+ if make_silent or builtins.MULTITOOL_TOGGLE_SILENT:
15
+ if exit_status is not None:
16
+ sys.exit(exit_status)
17
+ return
18
+
19
+ verbosity = max(current_verbosity, builtins.MULTITOOL_TOGGLE_VERBOSE)
20
+
21
+ if verbosity >= expected_verbosity:
22
+ print(*message, end=line_ending, flush=should_flush)
23
+
24
+ if exit_status is not None:
25
+ sys.exit(exit_status)
@@ -7,6 +7,7 @@ from multitool import console
7
7
 
8
8
 
9
9
  def wrap_with_exception_handling(func):
10
+
10
11
  @functools.wraps(func)
11
12
  def wrapper(*args, **kwargs):
12
13
  try:
@@ -0,0 +1,224 @@
1
+ import configparser
2
+ import os
3
+ import shutil
4
+ import stat
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from click_option_group import MutuallyExclusiveOptionGroup, optgroup
10
+
11
+ from multitool import (
12
+ APP,
13
+ MULTITOOL_PLUGINS_CONFIG_FILE,
14
+ MULTITOOL_PLUGINS_DIRECTORY,
15
+ MULTITOOL_PLUGINS_PATH,
16
+ console,
17
+ )
18
+ from multitool.silent import common_silent_options
19
+ from multitool.utils import (
20
+ create_directory,
21
+ create_empty_file,
22
+ execute_function_on_directory_files,
23
+ is_directory,
24
+ is_regular_file,
25
+ read_file_content,
26
+ )
27
+ from multitool.verbose import common_verbose_options
28
+
29
+ try:
30
+ from git import Repo
31
+
32
+ HAS_GIT = True
33
+ HELP = "Plugins manager for distributing commands."
34
+ except ImportError:
35
+ HAS_GIT = False
36
+ HELP = "Plugins manager. Git is required."
37
+
38
+
39
+ def require_git():
40
+ if not HAS_GIT:
41
+ sys.exit(0)
42
+
43
+
44
+ def init():
45
+ create_directory(MULTITOOL_PLUGINS_DIRECTORY)
46
+ create_empty_file(MULTITOOL_PLUGINS_PATH)
47
+ create_empty_file(MULTITOOL_PLUGINS_CONFIG_FILE)
48
+
49
+
50
+ def config(section="sources"):
51
+ cfg = configparser.ConfigParser(allow_no_value=True)
52
+ cfg.read(MULTITOOL_PLUGINS_CONFIG_FILE)
53
+ return dict(cfg._sections.get(section, {}))
54
+
55
+
56
+ def clone():
57
+ init()
58
+ for name, uri in config().items():
59
+ dest = Path(MULTITOOL_PLUGINS_DIRECTORY) / name
60
+ if is_directory(dest):
61
+ continue
62
+
63
+ console.echo(f"Installing {name}... ", line_ending="", should_flush=True)
64
+ try:
65
+ Repo.clone_from(uri, dest)
66
+ console.echo("Done")
67
+ except Exception as e:
68
+ console.echo(e)
69
+
70
+
71
+ def update_repos():
72
+
73
+ def fn(p):
74
+ if not is_directory(p):
75
+ return
76
+
77
+ console.echo(f"Updating {Path(p).stem}... ", line_ending="", should_flush=True)
78
+
79
+ try:
80
+ repo = Repo(p)
81
+ if not repo.bare:
82
+ repo.remotes["origin"].pull()
83
+ console.echo("Done")
84
+ except Exception as e:
85
+ console.echo(e)
86
+
87
+ execute_function_on_directory_files(MULTITOOL_PLUGINS_DIRECTORY, fn, glob="[!.][!__]*")
88
+
89
+
90
+ def prune():
91
+ sources = config()
92
+
93
+ def fn(p):
94
+ if not is_directory(p):
95
+ return
96
+
97
+ name = Path(p).stem
98
+ if name in sources:
99
+ return
100
+
101
+ console.echo(f"Removing {name}... ", line_ending="", should_flush=True)
102
+
103
+ try:
104
+ shutil.rmtree(p, onerror=_chmod)
105
+ console.echo("Done")
106
+ except Exception as e:
107
+ console.echo(e)
108
+
109
+ execute_function_on_directory_files(MULTITOOL_PLUGINS_DIRECTORY, fn, glob="[!.][!__]*")
110
+
111
+
112
+ def _chmod(func, p, _):
113
+ os.chmod(p, stat.S_IRWXU)
114
+ func(p)
115
+
116
+
117
+ # def prune():
118
+ # sources = config()
119
+
120
+ # def fn(p):
121
+ # if not is_directory(p):
122
+ # return
123
+
124
+ # name = Path(p).stem
125
+ # if name in sources:
126
+ # return
127
+
128
+ # console.echo(f"Removing {name}... ", line_ending="", should_flush=True)
129
+
130
+ # try:
131
+ # shutil.rmtree(p, onexc=_chmod)
132
+ # console.echo("Done")
133
+ # except Exception as e:
134
+ # console.echo(e)
135
+
136
+ # execute_function_on_directory_files(PLUGINS_DIR, fn, glob="[!.][!__]*")
137
+
138
+ # def _chmod(func, path, exc):
139
+ # os.chmod(path, stat.S_IRWXU)
140
+ # func(path)
141
+
142
+
143
+ def plugin_info(name):
144
+ f = Path(MULTITOOL_PLUGINS_DIRECTORY) / name / f"{APP}-info.json"
145
+
146
+ if not is_regular_file(f):
147
+ return None
148
+
149
+ return read_file_content(f, type="json")
150
+
151
+
152
+ def print_commit(name):
153
+ repo = Repo(Path(MULTITOOL_PLUGINS_DIRECTORY) / name)
154
+ console.echo(repo.git.log("--pretty=format:%h - %s (%cr) <%an>", "-1"))
155
+
156
+
157
+ @click.group(help=HELP)
158
+ def plugins():
159
+ pass
160
+
161
+
162
+ @plugins.command()
163
+ @common_silent_options
164
+ @common_verbose_options
165
+ @click.option("-a/-A", "--apply-changes/--no-apply-changes", default=False)
166
+ def configure(silent, verbose, apply_changes):
167
+ require_git()
168
+ init()
169
+
170
+ click.edit(filename=MULTITOOL_PLUGINS_CONFIG_FILE)
171
+
172
+ if apply_changes:
173
+ clone()
174
+ prune()
175
+ else:
176
+ console.echo(f"\nRun `{APP} plugins update` to apply changes.\n")
177
+
178
+
179
+ @plugins.command()
180
+ @common_silent_options
181
+ @common_verbose_options
182
+ def update(silent, verbose):
183
+ require_git()
184
+ clone()
185
+ update_repos()
186
+
187
+
188
+ @plugins.command()
189
+ @common_silent_options
190
+ @common_verbose_options
191
+ @click.option("-n", "--name")
192
+ @optgroup.group("Filter options", cls=MutuallyExclusiveOptionGroup)
193
+ @optgroup.option("--show-commit-only/--no-show-commit-only", default=False)
194
+ @optgroup.option("--show-dependencies-only/--no-show-dependencies-only", default=False)
195
+ def show(silent, verbose, name, show_commit_only, show_dependencies_only):
196
+ if not name:
197
+ for k, v in config().items():
198
+ console.echo(f"{k}: {v}")
199
+ return
200
+
201
+ info = plugin_info(name)
202
+ if not info:
203
+ return
204
+
205
+ if show_commit_only:
206
+ require_git()
207
+ print_commit(name)
208
+ return
209
+
210
+ if show_dependencies_only:
211
+ if info.get("Requires"):
212
+ console.echo(info["Requires"])
213
+ return
214
+
215
+ for k, v in info.items():
216
+ console.echo(f"{k}: {v}")
217
+
218
+
219
+ @plugins.command()
220
+ @common_silent_options
221
+ @common_verbose_options
222
+ def prune_cmd(silent, verbose):
223
+ require_git()
224
+ prune()
@@ -9,10 +9,10 @@ def silent_callback(ctx, param, value):
9
9
 
10
10
  def common_silent_options(func):
11
11
  func = click.option(
12
- '--silent',
13
- show_default='toggle silent output',
14
- flag_value=True,
15
- default=False,
16
- callback=silent_callback,
12
+ '--silent',
13
+ show_default='toggle silent output',
14
+ flag_value=True,
15
+ default=False,
16
+ callback=silent_callback,
17
17
  )(func)
18
18
  return func
@@ -15,7 +15,7 @@ def apply_function_on_iterable(iterable, func, state_op="append", args=(), kwarg
15
15
  kwargs = {}
16
16
  state = []
17
17
  for item in iterable:
18
- _tuple = (item,)
18
+ _tuple = (item, )
19
19
  result = func(*(_tuple + args), **kwargs)
20
20
  if result:
21
21
  getattr(state, state_op)(result)
@@ -26,9 +26,9 @@ def configure_global_logger(log_file):
26
26
  create_empty_file(log_file)
27
27
  remove_file_if_above_size(log_file, size_kb=1000)
28
28
  logging.basicConfig(
29
- filename=log_file,
30
- level=logging.WARNING,
31
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
29
+ filename=log_file,
30
+ level=logging.WARNING,
31
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
32
32
  )
33
33
 
34
34
 
@@ -57,7 +57,7 @@ def execute_function_on_directory_files(dir, func, glob="**/*", args=(), kwargs=
57
57
  kwargs = {}
58
58
  state = []
59
59
  for file_path in Path(get_resolved_directory_path(dir)).resolve().glob(glob):
60
- _tuple = (str(file_path),)
60
+ _tuple = (str(file_path), )
61
61
  result = func(*(_tuple + args), **kwargs)
62
62
  if result:
63
63
  state.append(result)
@@ -72,23 +72,22 @@ def get_resolved_directory_path(target_directory=None):
72
72
  return os.getcwd()
73
73
 
74
74
 
75
- def import_plugins_from_directory(plugins_init_file, existing_commands):
75
+ def import_plugins_from_directory(init_file, commands):
76
76
  try:
77
- spec = importlib.util.spec_from_file_location('plugins_modules', plugins_init_file)
77
+ spec = importlib.util.spec_from_file_location("plugins_modules", init_file)
78
78
  module = importlib.util.module_from_spec(spec)
79
79
  sys.modules[spec.name] = module
80
80
  spec.loader.exec_module(module)
81
- import plugins_modules # type: ignore
82
- from plugins_modules import __all__ as all_plugins_modules # type: ignore
83
81
 
84
- for module in all_plugins_modules:
85
- _module = getattr(plugins_modules, module)
86
- if isinstance(_module, (click.core.Command, click.core.Group)):
87
- existing_commands.add(_module)
82
+ plugins = getattr(module, "__all__", [])
83
+
84
+ for name in plugins:
85
+ obj = getattr(module, name)
86
+ if isinstance(obj, (click.Command, click.Group)):
87
+ commands.add(obj)
88
+
88
89
  except ImportError:
89
- logging.warning(
90
- f'{inspect.stack()[0][3]}; will skip loading plugin: {module}', exc_info=True
91
- )
90
+ logging.warning("Failed to load plugin", exc_info=True)
92
91
 
93
92
 
94
93
  def is_directory(d):
@@ -14,10 +14,10 @@ def verbose_callback(ctx, param, value):
14
14
 
15
15
  def common_verbose_options(func):
16
16
  func = click.option(
17
- '-v',
18
- '--verbose',
19
- show_default='toggle verbose output',
20
- count=True,
21
- callback=verbose_callback,
17
+ '-v',
18
+ '--verbose',
19
+ show_default='toggle verbose output',
20
+ count=True,
21
+ callback=verbose_callback,
22
22
  )(func)
23
23
  return func
@@ -1,31 +1,30 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: multitool
3
- Version: 0.5.0
4
- Summary: General-purpose command-line interface with plugins support
5
- Home-page: https://github.com/mdelotavo/multitool
6
- Author: Matthew Delotavo
7
- Author-email: matthew.t.delotavo@gmail.com
8
- License: MIT
9
- Platform: UNKNOWN
3
+ Version: 0.6.0
4
+ Summary: Quickly create and distribute 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
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Environment :: Console
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Operating System :: OS Independent
15
14
  Classifier: Programming Language :: Python :: 3.7
16
15
  Requires-Python: >=3.7
16
+ Description-Content-Type: text/x-rst
17
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
18
23
 
19
24
  multitool
20
25
  =========
21
26
 
22
- Multitool is a general-purpose command-line interface with plugins support.
23
-
24
- --------------------
25
- Why does this exist?
26
- --------------------
27
-
28
- I built this tool so I can quickly create and distribute command-line tools for consulting work and personal use.
27
+ Quickly create and distribute command-line tools with plugins.
29
28
 
30
29
  -----
31
30
  Usage
@@ -35,17 +34,14 @@ Usage
35
34
 
36
35
  Usage: multitool [OPTIONS] COMMAND [ARGS]...
37
36
 
38
- Welcome to the Multitool command-line interface!
39
-
40
- PyPI: https://pypi.org/project/multitool/
41
- GitHub: https://github.com/mdelotavo/multitool
37
+ Quickly create and distribute command-line tools.
42
38
 
43
39
  Options:
44
40
  -V, --version Show the version and exit.
45
41
  -h, --help Show this message and exit.
46
42
 
47
43
  Commands:
48
- plugins Simple plugins manager for distributing commands.
44
+ plugins Plugins manager for distributing commands.
49
45
 
50
46
  ----------------
51
47
  Managing plugins
@@ -176,5 +172,3 @@ Reviewing this file can help you diagnose installation problems, missing depende
176
172
  .. _`click`: https://click.palletsprojects.com/
177
173
  .. _`multitool-plugins`: https://github.com/mdelotavo/multitool-plugins
178
174
  .. _`public plugins repository`: https://github.com/mdelotavo/multitool-plugins
179
-
180
-
@@ -1,6 +1,6 @@
1
1
  LICENSE
2
2
  README.rst
3
- setup.py
3
+ pyproject.toml
4
4
  multitool/__init__.py
5
5
  multitool/__main__.py
6
6
  multitool/cls.py
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  multitool = multitool.__main__:main
3
-
@@ -1,4 +1,4 @@
1
+ click>=8.1.3
1
2
  click-aliases>=1.0.1
2
3
  click-option-group>=0.5.5
3
- click>=8.1.3
4
- gitpython>=3.1.30
4
+ GitPython>=3.1.30
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "multitool"
7
+ version = "0.6.0"
8
+ description = "Quickly create and distribute command-line tools."
9
+ readme = { file = "README.rst", content-type = "text/x-rst" }
10
+ requires-python = ">=3.7"
11
+ license = "MIT"
12
+
13
+ authors = [
14
+ { name = "Matthew Delotavo", email = "matthew.t.delotavo@gmail.com" }
15
+ ]
16
+
17
+ keywords = ["cli", "tooling", "automation"]
18
+
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Environment :: Console",
22
+ "Intended Audience :: Developers",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3.7"
25
+ ]
26
+
27
+ dependencies = [
28
+ "click>=8.1.3",
29
+ "click-aliases>=1.0.1",
30
+ "click-option-group>=0.5.5",
31
+ "GitPython>=3.1.30"
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/mdelotavo/multitool"
36
+ Repository = "https://github.com/mdelotavo/multitool"
37
+
38
+ [project.scripts]
39
+ multitool = "multitool.__main__:main"
40
+
41
+ [tool.setuptools]
42
+ include-package-data = true
43
+
44
+ [tool.setuptools.packages.find]
45
+ include = ["multitool*"]
@@ -5,6 +5,7 @@ from unittest.mock import patch
5
5
 
6
6
  from multitool.utils import show_message
7
7
 
8
+
8
9
  class TestUtils(TestCase):
9
10
 
10
11
  @patch('builtins.print')
@@ -1,23 +0,0 @@
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,266 +0,0 @@
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()
multitool-0.5.0/setup.py DELETED
@@ -1,46 +0,0 @@
1
- import os
2
-
3
- from multitool import APP
4
- from multitool import __version__ as version
5
- from multitool import description
6
-
7
- readme = os.path.join(os.path.dirname(__file__), 'README.rst')
8
- long_description = open(readme).read()
9
-
10
- SETUP_ARGS = dict(
11
- name=APP,
12
- version=version,
13
- description=(description),
14
- long_description=long_description,
15
- url='https://github.com/mdelotavo/multitool',
16
- author='Matthew Delotavo',
17
- author_email='matthew.t.delotavo@gmail.com',
18
- license='MIT',
19
- include_package_data=True,
20
- classifiers=[
21
- 'Development Status :: 4 - Beta',
22
- 'Environment :: Console',
23
- 'Intended Audience :: Developers',
24
- 'License :: OSI Approved :: MIT License',
25
- 'Operating System :: OS Independent',
26
- 'Programming Language :: Python :: 3.7',
27
- ],
28
- entry_points = {
29
- 'console_scripts': [
30
- 'multitool=multitool.__main__:main'
31
- ]
32
- },
33
- install_requires = [
34
- 'click>=8.1.3',
35
- 'click-aliases>=1.0.1',
36
- 'click-option-group>=0.5.5',
37
- 'gitpython>=3.1.30'
38
- ],
39
- python_requires=">=3.7",
40
- )
41
-
42
- if __name__ == '__main__':
43
- from setuptools import setup, find_packages
44
-
45
- SETUP_ARGS['packages'] = find_packages()
46
- setup(**SETUP_ARGS)
File without changes
File without changes
File without changes