skilleter-thingy 0.0.22__py3-none-any.whl

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.

Potentially problematic release.


This version of skilleter-thingy might be problematic. Click here for more details.

Files changed (67) hide show
  1. skilleter_thingy/__init__.py +0 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/aws.py +548 -0
  4. skilleter_thingy/borger.py +269 -0
  5. skilleter_thingy/colour.py +213 -0
  6. skilleter_thingy/console_colours.py +63 -0
  7. skilleter_thingy/dc_curses.py +278 -0
  8. skilleter_thingy/dc_defaults.py +221 -0
  9. skilleter_thingy/dc_util.py +50 -0
  10. skilleter_thingy/dircolors.py +308 -0
  11. skilleter_thingy/diskspacecheck.py +67 -0
  12. skilleter_thingy/docker.py +95 -0
  13. skilleter_thingy/docker_purge.py +113 -0
  14. skilleter_thingy/ffind.py +536 -0
  15. skilleter_thingy/files.py +142 -0
  16. skilleter_thingy/ggit.py +90 -0
  17. skilleter_thingy/ggrep.py +154 -0
  18. skilleter_thingy/git.py +1368 -0
  19. skilleter_thingy/git2.py +1307 -0
  20. skilleter_thingy/git_br.py +180 -0
  21. skilleter_thingy/git_ca.py +142 -0
  22. skilleter_thingy/git_cleanup.py +287 -0
  23. skilleter_thingy/git_co.py +220 -0
  24. skilleter_thingy/git_common.py +61 -0
  25. skilleter_thingy/git_hold.py +154 -0
  26. skilleter_thingy/git_mr.py +92 -0
  27. skilleter_thingy/git_parent.py +77 -0
  28. skilleter_thingy/git_review.py +1416 -0
  29. skilleter_thingy/git_update.py +385 -0
  30. skilleter_thingy/git_wt.py +96 -0
  31. skilleter_thingy/gitcmp_helper.py +322 -0
  32. skilleter_thingy/gitlab.py +193 -0
  33. skilleter_thingy/gitprompt.py +274 -0
  34. skilleter_thingy/gl.py +174 -0
  35. skilleter_thingy/gphotosync.py +610 -0
  36. skilleter_thingy/linecount.py +155 -0
  37. skilleter_thingy/logger.py +112 -0
  38. skilleter_thingy/moviemover.py +133 -0
  39. skilleter_thingy/path.py +156 -0
  40. skilleter_thingy/photodupe.py +110 -0
  41. skilleter_thingy/phototidier.py +248 -0
  42. skilleter_thingy/popup.py +87 -0
  43. skilleter_thingy/process.py +112 -0
  44. skilleter_thingy/py_audit.py +131 -0
  45. skilleter_thingy/readable.py +270 -0
  46. skilleter_thingy/remdir.py +126 -0
  47. skilleter_thingy/rmdupe.py +550 -0
  48. skilleter_thingy/rpylint.py +91 -0
  49. skilleter_thingy/run.py +334 -0
  50. skilleter_thingy/s3_sync.py +383 -0
  51. skilleter_thingy/splitpics.py +99 -0
  52. skilleter_thingy/strreplace.py +82 -0
  53. skilleter_thingy/sysmon.py +435 -0
  54. skilleter_thingy/tfm.py +920 -0
  55. skilleter_thingy/tfm_pane.py +595 -0
  56. skilleter_thingy/tfparse.py +101 -0
  57. skilleter_thingy/tidy.py +160 -0
  58. skilleter_thingy/trimpath.py +84 -0
  59. skilleter_thingy/window_rename.py +92 -0
  60. skilleter_thingy/xchmod.py +125 -0
  61. skilleter_thingy/yamlcheck.py +89 -0
  62. skilleter_thingy-0.0.22.dist-info/LICENSE +619 -0
  63. skilleter_thingy-0.0.22.dist-info/METADATA +22 -0
  64. skilleter_thingy-0.0.22.dist-info/RECORD +67 -0
  65. skilleter_thingy-0.0.22.dist-info/WHEEL +5 -0
  66. skilleter_thingy-0.0.22.dist-info/entry_points.txt +43 -0
  67. skilleter_thingy-0.0.22.dist-info/top_level.txt +1 -0
@@ -0,0 +1,322 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Script invoked via the 'git cmp' alias, which sets GIT_EXTERNAL_DIFF so that
5
+ this is called instead of using git diff.
6
+
7
+ Copyright (C) 2017-18 John Skilleter
8
+
9
+ Parameters passed by git are:
10
+
11
+ For a normal diff between two versions of a file in Git:
12
+
13
+ N MEANING EXAMPLE
14
+ 1 file path tgy_git.py
15
+
16
+ 2 tmp old file /tmp/OjfLZ8_tgy_git.py
17
+ 3 old SHA1 027422b8b6e945227b27abf4161ad38c5b6e9ff9
18
+ 4 old perm 100644
19
+
20
+ 5 tmp new file tgy_git.py
21
+ 6 new SHA1 0000000000000000000000000000000000000000
22
+ 7 new perm 100644
23
+
24
+ If the path is unmerged, only parameter #1 is passed
25
+
26
+ Also sets:
27
+
28
+ GIT_DIFF_PATH_COUNTER - incremented for each file compared
29
+ GIT_DIFF_PATH_TOTAL - total number of paths to be compared
30
+
31
+ If GIT_SUBDIR is set in the environment it indicates the relative
32
+ path of the current directory from the top-level directory of the
33
+ working tree.
34
+ """
35
+ ################################################################################
36
+
37
+ ################################################################################
38
+ # Imports
39
+
40
+ import sys
41
+ import os
42
+ import argparse
43
+ import filecmp
44
+ import re
45
+
46
+ from skilleter_thingy import logger
47
+ from skilleter_thingy import colour
48
+ from skilleter_thingy import run
49
+ from skilleter_thingy import files
50
+ from skilleter_thingy import git
51
+ from skilleter_thingy import dircolors
52
+
53
+ ################################################################################
54
+ # Constants
55
+
56
+ # A file must be at least this size to be considered binary - if it is smaller
57
+ # we give it the benefit of the doubt.
58
+
59
+ MIN_BINARY_SIZE = 8
60
+
61
+ ################################################################################
62
+
63
+ def report_permissions(perm):
64
+ """ Convert an octal value in a string to a description of file permissions
65
+ e.g. given '644' it will return 'rw-r--r--' """
66
+
67
+ mask_chars = ('r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x')
68
+
69
+ # Convert the permissions from an octal string to an integer
70
+
71
+ permissions = int(perm, 8)
72
+
73
+ # Start at the topmost bit and work downwards adding the mask character
74
+ # for bits that are set and '-' for ones that aren't.
75
+
76
+ mask = 1 << (len(mask_chars) - 1)
77
+
78
+ permtext = []
79
+
80
+ for mask_char in mask_chars:
81
+ permtext.append(mask_char if permissions & mask else '-')
82
+ mask >>= 1
83
+
84
+ return '%s' % ''.join(permtext)
85
+
86
+ ################################################################################
87
+
88
+ def main():
89
+ """ Main function - does everything """
90
+
91
+ # Set up logging
92
+
93
+ log = logger.init('gitcmp')
94
+
95
+ # Allow the log level to be configured in git config, as well as via the
96
+ # GITCMP_DEBUG
97
+
98
+ txt_debug = git.config_get('cmp', 'debug')
99
+ env_debug = os.getenv('GITCMP_DEBUG', '0')
100
+
101
+ if txt_debug.lower() in ('true', '1') or env_debug.lower() in ('true', '1'):
102
+ log.setLevel(logger.DEBUG)
103
+
104
+ # Parse the command line
105
+
106
+ parser = argparse.ArgumentParser(description='Invoked via the "git cmp" alias. Works as an enhanced version of "git difftool"')
107
+
108
+ parser.add_argument('file_path', nargs='?', help='File name and path')
109
+
110
+ parser.add_argument('old_file', nargs='?', help='Name of temporary copy of old version')
111
+ parser.add_argument('old_sha1', nargs='?', help='SHA1 of the old version')
112
+ parser.add_argument('old_perm', nargs='?', help='Permissions for the old version')
113
+
114
+ parser.add_argument('new_file', nargs='?', help='Name of temporary copy of the new version')
115
+ parser.add_argument('new_sha1', nargs='?', help='SHA1 of the new version')
116
+ parser.add_argument('new_perm', nargs='?', help='Permissions for the new version')
117
+
118
+ parser.add_argument('new_name', nargs='?', help='New name (if file has been renamed)')
119
+ parser.add_argument('rename', nargs='?', help='Description of rename')
120
+
121
+ args = parser.parse_args()
122
+
123
+ # Get configuration from the environment
124
+
125
+ path_count = int(os.getenv('GIT_DIFF_PATH_COUNTER', '0'))
126
+ path_total = int(os.getenv('GIT_DIFF_PATH_TOTAL', '0'))
127
+ diff_binaries = int(os.getenv('GIT_DIFF_BINARIES', '0'))
128
+ skip_deleted = int(os.getenv('GIT_IGNORE_DELETED', '0'))
129
+
130
+ # Debug output
131
+
132
+ log.info('Parameters to gitcmp-helper:')
133
+ log.info('1: path: %s', args.file_path)
134
+ log.info('2: old file: %s', args.old_file)
135
+ log.info('3: old sha1: %s', args.old_sha1)
136
+ log.info('4: old perm: %s', args.old_perm)
137
+ log.info('5: new file: %s', args.new_file)
138
+ log.info('6: new sha1: %s', args.new_sha1)
139
+ log.info('7: new perm: %s', args.new_perm)
140
+ log.info('8: new name: %s', args.new_name)
141
+ log.info('9: rename : %s', args.rename)
142
+ log.info('path count: %d/%d', path_count, path_total)
143
+
144
+ # Sanity checks
145
+
146
+ if args.file_path is None:
147
+ sys.stderr.write('At least one parameter must be specified\n')
148
+ sys.exit(1)
149
+
150
+ # Check and handle for the simple case of an unmerged file
151
+
152
+ if args.old_file is None:
153
+ colour.write('[CYAN:%s] is not merged' % args.file_path)
154
+ sys.exit(0)
155
+
156
+ # Make sure that we have all the expected parameters
157
+
158
+ if args.new_perm is None:
159
+ sys.stderr.write('Either 1 or 7 parameters must be specifed\n')
160
+ sys.exit(1)
161
+
162
+ # Make sure we can access the temporary files supplied
163
+
164
+ if not os.access(args.old_file, os.R_OK):
165
+ sys.stderr.write('Unable to read temporary old file: %s\n' % args.old_file)
166
+ sys.exit(2)
167
+
168
+ if not os.access(args.new_file, os.R_OK):
169
+ sys.stderr.write('Unable to read temporary new file: %s\n' % args.new_file)
170
+ sys.exit(2)
171
+
172
+ dc = dircolors.Dircolors()
173
+
174
+ # Determine the best way of reporting the path to the file
175
+
176
+ working_tree_path = os.getcwd()
177
+ current_path = os.path.join(working_tree_path, os.getenv('GIT_SUBDIR', ''))
178
+
179
+ current_file_path = os.path.relpath(args.file_path if args.new_name is None else args.new_name, current_path)
180
+
181
+ log.info('file path: %s', current_file_path)
182
+
183
+ # Heading printed first
184
+
185
+ heading = ['[BOLD]Changes in [NORMAL]%s' % dc.format(current_file_path)]
186
+
187
+ # If file was renamed, append the old name and the degree of similarity
188
+
189
+ if args.new_name:
190
+ similarity = re.sub(r'(similarity index) (.*)', r'\1 [CYAN:\2]', args.rename.split('\n')[0])
191
+
192
+ heading.append('(rename from %s with %s)' % (dc.format(os.path.relpath(args.file_path, current_path)), similarity))
193
+
194
+ # If processing more than one file, append he index and total number of files
195
+
196
+ if path_total > 0:
197
+ heading.append('(%d/%d)' % (path_count, path_total))
198
+
199
+ # Check for newly created/deleted files (other version will be '/dev/null')
200
+
201
+ created_file = (args.old_file == '/dev/null')
202
+ deleted_file = (args.new_file == '/dev/null')
203
+
204
+ if created_file:
205
+ heading.append('(new file)')
206
+
207
+ if deleted_file:
208
+ heading.append('(deleted file)')
209
+
210
+ colour.write(' '.join(heading))
211
+
212
+ # Report permission(s) / permissions changes
213
+
214
+ permissions_changed = not (created_file or deleted_file) and args.old_perm != args.new_perm
215
+
216
+ if deleted_file:
217
+ colour.write(' Old permissions: [CYAN:%s]' % report_permissions(args.old_perm))
218
+ elif created_file:
219
+ colour.write(' New permissions: [CYAN:%s]' % report_permissions(args.new_perm))
220
+ elif permissions_changed:
221
+ colour.write(' Changed permissions: [CYAN:%s] -> [CYAN:%s]' % (report_permissions(args.old_perm), report_permissions(args.new_perm)))
222
+ else:
223
+ colour.write(' Permissions: [CYAN:%s]' % report_permissions(args.new_perm))
224
+
225
+ # Report size changes
226
+
227
+ old_size = os.stat(args.old_file).st_size
228
+ new_size = os.stat(args.new_file).st_size
229
+
230
+ formatted_old_size = files.format_size(old_size, always_suffix=True)
231
+ formatted_new_size = files.format_size(new_size, always_suffix=True)
232
+
233
+ if created_file:
234
+ colour.write(' New size: [CYAN:%s]' % formatted_new_size)
235
+ elif deleted_file:
236
+ colour.write(' Original size: [CYAN:%s]' % formatted_old_size)
237
+ elif new_size == old_size:
238
+ colour.write(' Size: [CYAN]%s[NORMAL] (no change)' % formatted_new_size)
239
+ else:
240
+ formatted_delta_size = files.format_size(abs(new_size - old_size), always_suffix=True)
241
+
242
+ delta = '%s %s' % (formatted_delta_size, 'larger' if new_size > old_size else 'smaller')
243
+
244
+ if formatted_old_size == formatted_new_size:
245
+ colour.write(' Size: [CYAN:%s] (%s)' % (formatted_new_size, delta))
246
+ else:
247
+ colour.write(' Size: [CYAN:%s] -> [CYAN:%s] (%s)' %
248
+ (formatted_old_size, formatted_new_size, delta))
249
+
250
+ # Report file type
251
+
252
+ if created_file:
253
+ old_type = None
254
+ else:
255
+ old_type = files.file_type(args.old_file)
256
+
257
+ if deleted_file:
258
+ new_type = None
259
+ else:
260
+ new_type = files.file_type(args.new_file)
261
+
262
+ if created_file:
263
+ colour.write(' File type: [CYAN:%s]' % new_type)
264
+ elif deleted_file:
265
+ colour.write(' Original file type: [CYAN:%s]' % old_type)
266
+ elif old_type != new_type:
267
+ colour.write(' File type: [CYAN:%s] (previously [CYAN:%s)]' % (new_type, old_type))
268
+ else:
269
+ colour.write(' File type: [CYAN:%s]' % new_type)
270
+
271
+ # Report permissions and type
272
+
273
+ if filecmp.cmp(args.old_file, args.new_file, shallow=False):
274
+ # If the file is unchanged, just report the permissions change (if any)
275
+
276
+ if permissions_changed:
277
+ colour.write(' Revisions are identical with changes to permissions')
278
+ else:
279
+ colour.write(' Revisions are identical')
280
+ else:
281
+ # Check if the file is/was a binary
282
+
283
+ old_binary = not created_file and old_size > MIN_BINARY_SIZE and files.is_binary_file(args.old_file)
284
+ new_binary = not deleted_file and new_size > MIN_BINARY_SIZE and files.is_binary_file(args.new_file)
285
+
286
+ # If both versions are binary and we're not risking diffing binaries, report it
287
+ # otherwise, issue a warning if one version is binary then do the diff
288
+
289
+ if (old_binary or new_binary) and not diff_binaries:
290
+ colour.write(' Cannot diff binary files')
291
+ else:
292
+ difftool = git.config_get('cmp', 'difftool', defaultvalue='diffuse')
293
+
294
+ if old_binary or new_binary:
295
+ colour.write(' [BOLD:WARNING]: One or both files may be binaries')
296
+
297
+ if not deleted_file or not skip_deleted:
298
+ try:
299
+ run.run([difftool, args.old_file, args.new_file])
300
+ except run.RunError as exc:
301
+ print('Diff failed: %s' % exc.msg)
302
+
303
+ # Separate reports with a blank line
304
+
305
+ print('')
306
+
307
+ ################################################################################
308
+
309
+ def gitcmp_helper():
310
+ """Entry point"""
311
+
312
+ try:
313
+ main()
314
+ except KeyboardInterrupt:
315
+ sys.exit(1)
316
+ except BrokenPipeError:
317
+ sys.exit(2)
318
+
319
+ ################################################################################
320
+
321
+ if __name__ == '__main__':
322
+ gitcmp_helper()
@@ -0,0 +1,193 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ GitLab module - implemented using the REST API as some features are not
5
+ available (or don't work) in the official Python module
6
+
7
+ Copyright (C) 2017-20 John Skilleter
8
+
9
+ Licence: GPL v3 or later
10
+
11
+ Note: There are two types of function for returning data from GitLab;
12
+ the paged functions and the non-paged ones - the paged ones return a page
13
+ (normally 20 items) of data and need to be called repeated until no data is
14
+ left whereas the non-paged ones query all the data and concatenate it
15
+ together.
16
+
17
+ The paged functions expect a full request string with the URL, as returned
18
+ by the request_string() member. The non-paged ones call request_string()
19
+ to add the URL & API prefix.
20
+ """
21
+ ################################################################################
22
+
23
+ import sys
24
+ import os
25
+
26
+ try:
27
+ import requests
28
+ except ModuleNotFoundError:
29
+ sys.stderr.write('This code requires the Python "requests" module which should be installed via Pip\n')
30
+ sys.exit(1)
31
+
32
+ ################################################################################
33
+
34
+ class GitLabError(Exception):
35
+ """ Gitlab exceptions """
36
+
37
+ def __init__(self, response):
38
+ """ Save the error code and text """
39
+
40
+ self.status = response.status_code
41
+ self.message = response.reason
42
+
43
+ def __str__(self):
44
+ """ Return a string version of the exception """
45
+
46
+ return '%s: %s' % (self.status, self.message)
47
+
48
+ ################################################################################
49
+
50
+ class GitLab:
51
+ """ Class for GitLab access """
52
+
53
+ def __init__(self, gitlab, token=None):
54
+ """ Initialisation """
55
+
56
+ # Save the GitLab URL
57
+
58
+ self.gitlab = gitlab
59
+
60
+ # If we have a private token use it, otherwise try and get it from
61
+ # the environmnet
62
+
63
+ self.token = token if token else os.getenv('GITLAB_TOKEN', None)
64
+
65
+ # Create the default header for requests
66
+
67
+ self.header = {'Private-Token': self.token}
68
+
69
+ ################################################################################
70
+
71
+ @staticmethod
72
+ def encode_project(name):
73
+ """ Encode a project name in the form request by GitLab requests """
74
+
75
+ return name.replace('/', '%2F')
76
+
77
+ ################################################################################
78
+
79
+ def request_string(self, request):
80
+ """ Add the URL/API header onto a request string """
81
+
82
+ return '%s/api/v4/%s' % (self.gitlab, request)
83
+
84
+ ################################################################################
85
+
86
+ def request(self, request, parameters=None):
87
+ """ Send a request to GitLab - handles pagination and returns all the
88
+ results concatenated together """
89
+
90
+ if parameters:
91
+ request = '%s?%s' % (request, '&'.join(parameters))
92
+
93
+ gl_request = self.request_string(request)
94
+
95
+ # Keep requesting data until there's no 'next' link in the response
96
+
97
+ while True:
98
+ response = requests.get(gl_request, headers=self.header)
99
+
100
+ if not response:
101
+ raise GitLabError(response)
102
+
103
+ yield response.json()
104
+
105
+ if 'next' not in response.links:
106
+ break
107
+
108
+ gl_request = response.links['next']['url']
109
+
110
+ ################################################################################
111
+
112
+ def paged_request(self, request):
113
+ """ Send a request to GitLab - returns all the results concatenated together
114
+ and returns a page of results along with the request for the next page of
115
+ results (if any).
116
+
117
+ Note that the request parameter is the full request string as returned by
118
+ request_string(). """
119
+
120
+ response = requests.get(request, headers=self.header)
121
+
122
+ result = response.json()
123
+
124
+ if not response:
125
+ raise GitLabError(response)
126
+
127
+ request = response.links['next']['url'] if 'next' in response.links else None
128
+
129
+ return result, request
130
+
131
+ ################################################################################
132
+
133
+ def projects(self):
134
+ """ Return a list of projects """
135
+
136
+ return self.request('projects')
137
+
138
+ ################################################################################
139
+
140
+ def branches(self, repo):
141
+ """ Return the list of branches in a repo """
142
+
143
+ for batch in self.request('projects/%s/repository/branches' % self.encode_project(repo)):
144
+ for branch in batch:
145
+ yield branch
146
+
147
+ ################################################################################
148
+
149
+ def merge_requests(self, **kwargs):
150
+ """ Return a list of merge requests filtered according to the parameters """
151
+
152
+ request = 'merge_requests'
153
+
154
+ parameters = []
155
+
156
+ for data in kwargs:
157
+ parameters.append('%s=%s' % (data, kwargs[data]))
158
+
159
+ for result in self.request(request, parameters):
160
+ for r in result:
161
+ yield r
162
+
163
+ ################################################################################
164
+
165
+ def default_branch(self, repo):
166
+ """ Query gitlab to retreive the default branch for the repo """
167
+
168
+ # Look for the default branch
169
+
170
+ for branch in self.branches(repo):
171
+ if branch['default']:
172
+ return branch['name']
173
+
174
+ return None
175
+
176
+ ################################################################################
177
+
178
+ def isbranch(self, repo, branchname):
179
+ """ Return True if the branch exists in the repo """
180
+
181
+ request = self.request_string('projects/%s/repository/branches' % self.encode_project(repo))
182
+
183
+ while True:
184
+ branches, request = self.paged_request(request)
185
+
186
+ for branch in branches:
187
+ if branch['name'] == branchname:
188
+ return True
189
+
190
+ if not request:
191
+ break
192
+
193
+ return False