git-project 0.0.35__tar.gz → 0.0.36__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 (61) hide show
  1. {git_project-0.0.35 → git_project-0.0.36}/PKG-INFO +1 -1
  2. git_project-0.0.36/_version.py +24 -0
  3. git_project-0.0.36/build/lib/git_project/__init__.py +33 -0
  4. git_project-0.0.36/build/lib/git_project/commandline.py +164 -0
  5. git_project-0.0.36/build/lib/git_project/configobj.py +300 -0
  6. git_project-0.0.36/build/lib/git_project/exception.py +30 -0
  7. git_project-0.0.36/build/lib/git_project/git.py +873 -0
  8. git_project-0.0.36/build/lib/git_project/gitproject.py +61 -0
  9. git_project-0.0.36/build/lib/git_project/main.py +63 -0
  10. git_project-0.0.36/build/lib/git_project/parsermanager.py +360 -0
  11. git_project-0.0.36/build/lib/git_project/plugin.py +118 -0
  12. git_project-0.0.36/build/lib/git_project/pluginmanager.py +54 -0
  13. git_project-0.0.36/build/lib/git_project/project.py +197 -0
  14. git_project-0.0.36/build/lib/git_project/runnable.py +71 -0
  15. git_project-0.0.36/build/lib/git_project/scopedobj.py +207 -0
  16. git_project-0.0.36/build/lib/git_project/shell.py +119 -0
  17. git_project-0.0.36/build/lib/git_project/substitutable.py +139 -0
  18. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/main.py +4 -2
  19. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/runnable.py +1 -1
  20. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/shell.py +4 -0
  21. git_project-0.0.36/test.out +18 -0
  22. {git_project-0.0.35 → git_project-0.0.36}/tests/test_runnable.py +16 -0
  23. git_project-0.0.36/tests/test_shell.py +28 -0
  24. git_project-0.0.35/.git +0 -1
  25. git_project-0.0.35/_version.py +0 -21
  26. {git_project-0.0.35 → git_project-0.0.36}/.gitignore +0 -0
  27. {git_project-0.0.35 → git_project-0.0.36}/CONTRIBUTORS +0 -0
  28. {git_project-0.0.35 → git_project-0.0.36}/COPYING +0 -0
  29. {git_project-0.0.35 → git_project-0.0.36}/MANIFEST.in +0 -0
  30. {git_project-0.0.35 → git_project-0.0.36}/docs/Makefile +0 -0
  31. {git_project-0.0.35 → git_project-0.0.36}/docs/authors.rst +0 -0
  32. {git_project-0.0.35 → git_project-0.0.36}/docs/changelog.rst +0 -0
  33. {git_project-0.0.35 → git_project-0.0.36}/docs/conf.py +0 -0
  34. {git_project-0.0.35 → git_project-0.0.36}/docs/index.rst +0 -0
  35. {git_project-0.0.35 → git_project-0.0.36}/docs/intro.rst +0 -0
  36. {git_project-0.0.35 → git_project-0.0.36}/docs/license.rst +0 -0
  37. {git_project-0.0.35 → git_project-0.0.36}/docs/make.bat +0 -0
  38. {git_project-0.0.35 → git_project-0.0.36}/pyproject.toml +0 -0
  39. {git_project-0.0.35 → git_project-0.0.36}/requirements.txt +0 -0
  40. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/__init__.py +0 -0
  41. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/commandline.py +0 -0
  42. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/configobj.py +0 -0
  43. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/exception.py +0 -0
  44. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/git.py +0 -0
  45. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/gitproject.py +0 -0
  46. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/parsermanager.py +0 -0
  47. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/plugin.py +0 -0
  48. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/pluginmanager.py +0 -0
  49. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/project.py +0 -0
  50. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/scopedobj.py +0 -0
  51. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/substitutable.py +0 -0
  52. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/test_support/__init__.py +0 -0
  53. {git_project-0.0.35 → git_project-0.0.36}/src/git_project/test_support/common.py +0 -0
  54. {git_project-0.0.35 → git_project-0.0.36}/tests/conftest.py +0 -0
  55. {git_project-0.0.35 → git_project-0.0.36}/tests/test_confobj.py +0 -0
  56. {git_project-0.0.35 → git_project-0.0.36}/tests/test_git.py +0 -0
  57. {git_project-0.0.35 → git_project-0.0.36}/tests/test_gitproject.py +0 -0
  58. {git_project-0.0.35 → git_project-0.0.36}/tests/test_main.py +0 -0
  59. {git_project-0.0.35 → git_project-0.0.36}/tests/test_project.py +0 -0
  60. {git_project-0.0.35 → git_project-0.0.36}/tests/test_scopedobj.py +0 -0
  61. {git_project-0.0.35 → git_project-0.0.36}/tests/test_substitutable.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-project
3
- Version: 0.0.35
3
+ Version: 0.0.36
4
4
  Summary: The extensible stupid project manager
5
5
  Author-email: "David A. Greene" <dag@obbligato.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.36'
22
+ __version_tuple__ = version_tuple = (0, 0, 36)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2020 David A. Greene
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it under
6
+ # the terms of the GNU General Public License as published by the Free Software
7
+ # Foundation, either version 3 of the License, or (at your option) any later
8
+ # version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but WITHOUT
11
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License along with
16
+ # this program. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ from .commandline import parse_arguments, add_top_level_command
20
+ from .commandline import get_or_add_top_level_command
21
+ from .configobj import ConfigObject
22
+ from .exception import GitProjectException
23
+ from .git import Git
24
+ from .gitproject import GitProject
25
+ from .main import main_impl
26
+ from .parsermanager import ParserManager
27
+ from .plugin import Plugin
28
+ from .pluginmanager import PluginManager
29
+ from .project import Project
30
+ from .runnable import RunnableConfigObject
31
+ from .scopedobj import ScopedConfigObject
32
+ from .substitutable import SubstitutableConfigObject
33
+ from .shell import run_command_with_shell, iter_command, capture_command
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2020 David A. Greene
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it under
6
+ # the terms of the GNU General Public License as published by the Free Software
7
+ # Foundation, either version 3 of the License, or (at your option) any later
8
+ # version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but WITHOUT
11
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License along with
16
+ # this program. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+ """git-project: Manage project work.
19
+
20
+ Summary:
21
+
22
+ git project <command> [<options>]
23
+
24
+ Commands are registered by plugins and show with 'git project --help.' Each
25
+ command also has a --help option to explore workings of th command.
26
+
27
+ Users may link to git-project with a name appropriate to the project (for
28
+ example, git-fizzbin to invoke as 'git fizzbin <command> [<options>]).
29
+ git-project will then place all configuration in a section named after the link
30
+ ('fizzbin' in this case). This way one may have multiple projects active in a
31
+ single repository by invoking git-project via various links.
32
+
33
+ """
34
+
35
+ import argparse
36
+ import getpass
37
+ import io
38
+ import os
39
+ from pathlib import Path
40
+ import pygit2
41
+ import re
42
+ import shlex
43
+ import subprocess
44
+ import sys
45
+ import urllib.parse
46
+
47
+ try:
48
+ from importlib import metadata
49
+ except ImportError:
50
+ # Running on pre-3.8 Python; use importlib-metadata package
51
+ import importlib_metadata as metadata
52
+
53
+ from .parsermanager import ParserManager
54
+
55
+ def get_version_string(package):
56
+ version = metadata.version(package)
57
+
58
+ return f'{package} version {version}'
59
+
60
+ def add_version_argument(parser, package):
61
+ parser.add_argument('--version',
62
+ action='version',
63
+ version=get_version_string(package),
64
+ help='Print version')
65
+
66
+ def parse_arguments(git, gitproject, project, plugin_manager, args):
67
+ """Register plugin comand-line arguments. Parse command-link arguments and
68
+ invoke command functions.
69
+
70
+ git: Git object to examine the repository.
71
+
72
+ gitproject: GitProject object to provide cross-project configuration.
73
+
74
+ project: Project object to provide project-specific configuration.
75
+
76
+ plugin_manager: A manager holding available plugins.
77
+
78
+ """
79
+ parser_manager = ParserManager(gitproject, project)
80
+
81
+ parser = parser_manager.find_parser('__main__')
82
+
83
+ # --version
84
+ add_version_argument(parser, 'git-project')
85
+
86
+ command_subparser = parser_manager.add_subparser(parser,
87
+ 'command',
88
+ dest='command',
89
+ help='commands')
90
+ command_subparser.required = True
91
+
92
+ for plugin in plugin_manager.iterplugins():
93
+ plugin.add_arguments(git,
94
+ gitproject,
95
+ project,
96
+ parser_manager,
97
+ plugin_manager)
98
+
99
+ # Once all plugins have added arguments, give them a chance to modify
100
+ # arguments other plugsin may have added.
101
+ for plugin in plugin_manager.iterplugins():
102
+ plugin.modify_arguments(git,
103
+ gitproject,
104
+ project,
105
+ parser_manager,
106
+ plugin_manager)
107
+
108
+ clargs = parser_manager.parse_args(args)
109
+
110
+ if not hasattr(clargs, 'func'):
111
+ parser_manager.error()
112
+ sys.exit(1)
113
+
114
+ return clargs
115
+
116
+ def add_top_level_command(parser_manager,
117
+ name,
118
+ key,
119
+ **kwargs):
120
+ """Create a top-level command. Throw an exception if the command already
121
+ exists.
122
+
123
+ parser_manager - The active ParserManager.
124
+
125
+ name - The name of the command to be invoked.
126
+
127
+ key - A unique identifier for the created parser.
128
+
129
+ kwargs - Passthrough to argparse parser.
130
+
131
+ """
132
+
133
+ command_subparser = parser_manager.find_subparser('command')
134
+ assert command_subparser
135
+ parser = parser_manager.add_parser(command_subparser,
136
+ name,
137
+ key,
138
+ **kwargs)
139
+
140
+ return parser
141
+
142
+ def get_or_add_top_level_command(parser_manager,
143
+ name,
144
+ key,
145
+ **kwargs):
146
+ """Retrieve a top-level command parser or create one if none exists.
147
+
148
+ parser_manager - The active ParserManager.
149
+
150
+ name - The name of the command to be invoked.
151
+
152
+ key - A unique identifier for the created parser.
153
+
154
+ kwargs - Passthrough to argparse parser.
155
+
156
+ """
157
+
158
+ parser = parser_manager.find_parser(key)
159
+ if not parser:
160
+ parser = add_top_level_command(parser_manager,
161
+ name,
162
+ key,
163
+ **kwargs)
164
+ return parser
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2020 David A. Greene
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it under
6
+ # the terms of the GNU General Public License as published by the Free Software
7
+ # Foundation, either version 3 of the License, or (at your option) any later
8
+ # version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but WITHOUT
11
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License along with
16
+ # this program. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ import collections
20
+ import inspect
21
+
22
+ class ConfigObject(object):
23
+ """Base class for objects that use git-config as a backing store. Specified
24
+ property values are saved to the git config file and read from the config
25
+ file upon instantiation.
26
+ """
27
+ def __init__(self,
28
+ git,
29
+ project_section,
30
+ subsection,
31
+ ident,
32
+ **kwargs):
33
+ """ConfigObject construction. This should be treated as a private method and
34
+ all construction should occur through the get method.
35
+
36
+ git: An object to query the repository and make config changes.
37
+
38
+ project_section: git config section of the active project.
39
+
40
+ subsection: An arbitrarily-long subsection appended to project_section
41
+
42
+ ident: The name of this specific ConfigObject.
43
+
44
+ **kwargs: Keyword arguments of property values to set upon construction.
45
+
46
+ """
47
+
48
+ # We don't want the section to introduce subsections, so translate . to
49
+ # -. Since underscore is not allowed in a config key, translate them to -
50
+ # as well.
51
+
52
+ tr_table = project_section.maketrans('._', '--')
53
+ project_section = project_section.translate(tr_table)
54
+
55
+ # Also with pygit2 the key can't start with - so prepend a legal string.
56
+
57
+ if project_section.startswith('-'):
58
+ project_section = 'ZZ' + project_section
59
+
60
+ self._git = git
61
+ self._project_section = project_section
62
+ self._subsection = subsection
63
+ self._ident = ident
64
+ self._section = ConfigObject._get_full_section(self._project_section,
65
+ self._subsection,
66
+ self._ident)
67
+ self._init_from_dict(kwargs)
68
+
69
+ def __setattr__(self, name, value):
70
+ if name.startswith('_'):
71
+ super().__setattr__(name, value)
72
+ return
73
+
74
+ # The git config can only handle string values.
75
+ assert(isinstance(value, str))
76
+ self._set_item(name, value)
77
+
78
+ def __delattr__(self, name):
79
+ if name.startswith('_'):
80
+ super().__delattr__(name)
81
+ return
82
+ self.rm_items(name)
83
+
84
+ @classmethod
85
+ def get(cls,
86
+ git,
87
+ project_section,
88
+ subsection,
89
+ ident,
90
+ **kwargs):
91
+ """Factory to construct ConfigObjects.
92
+
93
+ cls: The derived class being constructed.
94
+
95
+ git: An object to query the repository and make config changes.
96
+
97
+ project_section: git config section of the active project.
98
+
99
+ subsection: An arbitrarily-long subsection appended to project_section
100
+
101
+ ident: The name of this specific ConfigObject.
102
+
103
+ **kwargs: Keyword arguments of property values to set upon construction.
104
+
105
+ """
106
+ inits = dict()
107
+
108
+ gitsection = ConfigObject._get_full_section(project_section,
109
+ subsection,
110
+ ident)
111
+ if git.has_repo():
112
+ config_section = git.config.get_section(gitsection)
113
+ if config_section:
114
+ for key, item in config_section:
115
+ name = key.rsplit('.', 1)[-1]
116
+ if item.is_multival():
117
+ inits[name] = [value for value in item.itervalues()]
118
+ else:
119
+ inits[name] = item.get_value()
120
+
121
+ for key, value in kwargs.items():
122
+ inits[key] = value
123
+
124
+ result = cls(git,
125
+ project_section,
126
+ subsection,
127
+ ident,
128
+ **inits)
129
+
130
+ return result
131
+
132
+ @classmethod
133
+ def exists(cls,
134
+ git,
135
+ project_section,
136
+ subsection,
137
+ ident):
138
+ """Return whether an existing git config exists for the ConfigObject.
139
+
140
+ cls: The derived class being checked.
141
+
142
+ git: An object to query the repository and make config changes.
143
+
144
+ project_section: git config section of the active project.
145
+
146
+ subsection: An arbitrarily-long subsection appended to project_section
147
+
148
+ ident: The name of this specific ConfigObject.
149
+
150
+ """
151
+ if not git.has_repo():
152
+ return False
153
+
154
+ gitsection = ConfigObject._get_full_section(project_section,
155
+ subsection,
156
+ ident)
157
+
158
+ section = git.config.get_section(gitsection)
159
+ return True if section else False
160
+
161
+ @staticmethod
162
+ def _get_full_section(section, subsection, ident):
163
+ """Construct a full git section name by appending subsection and ident (if not
164
+ None) to section.
165
+
166
+ """
167
+ result = section
168
+ if subsection:
169
+ result += '.' + subsection
170
+ if ident:
171
+ result += '.' + ident
172
+ return result
173
+
174
+ @classmethod
175
+ def get_managing_command(cls):
176
+ """Return a command-line command key that manages the ConfigObject. Derived
177
+ classes should override this method if they are managed by a
178
+ command-line command.
179
+
180
+ """
181
+ return None
182
+
183
+ @classmethod
184
+ def _add_property(cls, name):
185
+ """Add a property name that reads the git config when accessed and writes the
186
+ git config when written."""
187
+ def fun_get(self):
188
+ result = {item for item in self.iter_multival(name)}
189
+ if not result:
190
+ return None
191
+ if len(result) == 1:
192
+ return result.pop()
193
+ return frozenset(result)
194
+ def fun_set(self, value):
195
+ self._set_item(name, value)
196
+ prop = property(fun_get, fun_set)
197
+ setattr(cls, name, prop)
198
+
199
+ def has_item(self, name):
200
+ """Return whether self.<name> exists. We don't want to use hasattr because
201
+ <name> is actually set on the class, not the instance.
202
+
203
+ """
204
+ if not isinstance(getattr(type(self), name, None), property):
205
+ return False
206
+
207
+ # It's not quite enough to know we have a property. We might have a
208
+ # property from a previous instance but the git config entry in this
209
+ # instance might not be set. This should not happen during normal
210
+ # operation as the command is quickly executed and the program
211
+ # terminates. However, testing can have long-lived classes with many
212
+ # properties added to them.
213
+ if not self._git.has_repo():
214
+ return False
215
+
216
+ return self._git.config.has_item(self.get_section(), name)
217
+
218
+ def get_ident(self):
219
+ """Return the identifier of this object."""
220
+ return self._ident
221
+
222
+ def get_section(self):
223
+ """Return the section of this object."""
224
+ return self._section
225
+
226
+ def get_subsection(self):
227
+ """Return the subsection of this object."""
228
+ return self._subsection
229
+
230
+ def _set_item(self, name, value):
231
+ """Set property name to value and write it to the git config."""
232
+ if not hasattr(self.__class__, name):
233
+ self._add_property(name)
234
+ self._git.config.set_item(self._section, name, value)
235
+
236
+ def rm_item(self, name, pattern):
237
+ """Remove property name values matching pattern from the git config."""
238
+ self._git.config.rm_item(self._section, name, pattern)
239
+
240
+ def rm_items(self, name):
241
+ """Remove property name completely from the git config."""
242
+ self._git.config.rm_items(self._section, name)
243
+ delattr(self.__class__, name)
244
+
245
+ def _get_item(self, name):
246
+ """Get the value for property 'name.'"""
247
+ return self._git.config.get_item(self._section, name)
248
+
249
+ def add_item(self, name, value):
250
+ """Add an item with key name set to value. Rather than overwriting an existing
251
+ entry, add a new one to the git config, creeating a multi-value key.
252
+
253
+ """
254
+ if not hasattr(self, name):
255
+ self._add_property(name)
256
+ self._git.config.add_item(self._section, name, value)
257
+
258
+ def _init_from_dict(self, values):
259
+ """Set properties of the object using the mapping in dictionary valuees. Mapped
260
+ valuees that are sequences create multi-value keys.
261
+
262
+ """
263
+ def issequence(obj):
264
+ if isinstance(obj, str):
265
+ return False
266
+ return isinstance(obj, collections.abc.Sequence)
267
+
268
+ for key, value in values.items():
269
+ if issequence(value):
270
+ for v in value:
271
+ self.add_item(key, v)
272
+ else:
273
+ self._set_item(key, value)
274
+
275
+ def iter_multival(self, name):
276
+ """Iterate over the multiple values of a multi-value git config key."""
277
+ for value in self._git.config.iter_multival(self._section, name):
278
+ yield value
279
+
280
+ def __repr__(self):
281
+ """Serialize the object as a string."""
282
+ return str({key:value for (key, value) in
283
+ self.__dict__.items() if not key.startswith('_')})
284
+
285
+ def __str__(self):
286
+ """Serialize the object as a string."""
287
+ return str(self.__repr__())
288
+
289
+ def rm(self):
290
+ """Remove the entire section of this object from the git config."""
291
+ for key, value in self.iteritems():
292
+ self.rm_items(key)
293
+ # Removing all section entries removes the section.
294
+ #self._git.config.rm_section(self._section)
295
+
296
+ def iteritems(self):
297
+ """Iterate over all key, value items."""
298
+ for key, value in inspect.getmembers(self):
299
+ if self.has_item(key):
300
+ yield key, value
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2020 David A. Greene
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it under
6
+ # the terms of the GNU General Public License as published by the Free Software
7
+ # Foundation, either version 3 of the License, or (at your option) any later
8
+ # version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful, but WITHOUT
11
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
+ # details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License along with
16
+ # this program. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ from pathlib import Path
20
+ import sys
21
+
22
+ class GitProjectException(Exception):
23
+ def __init__(self, message):
24
+ super().__init__(message)
25
+ self._message = message
26
+
27
+ @property
28
+ def message(self):
29
+ command = Path(sys.argv[0]).name
30
+ return f'{command}: {self._message}'