skilleter-modules 0.0.3__py3-none-any.whl → 0.0.5__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.
@@ -272,57 +272,3 @@ def warning(txt, newline=True, stream=sys.stderr, prefix=False):
272
272
  write('[RED:WARNING]: ', newline=False, stream=stream, )
273
273
 
274
274
  write(txt, newline, stream)
275
-
276
- ################################################################################
277
-
278
- if __name__ == '__main__':
279
- for combo in (0, 1, 2):
280
- print()
281
- if combo == 0:
282
- print('Background colours')
283
- elif combo == 1:
284
- print('Foreground colours')
285
- else:
286
- print('Combinations')
287
-
288
- print()
289
- for y in range(0, 16):
290
- for x in range(0, 16):
291
- colour = x + y * 16
292
-
293
- if combo == 0:
294
- write(format('[B%d]%4d' % (colour, colour)), newline=False)
295
- elif combo == 1:
296
- write(format('[%d]%4d' % (colour, colour)), newline=False)
297
- else:
298
- write(format('[B%d]%4d[%d]/%4d ' % (colour, colour, 255 - colour, 255 - colour)), newline=False)
299
-
300
- write('[NORMAL]')
301
-
302
- print()
303
-
304
- write('Foreground: [RED]red [GREEN]green [BLACK]black [NORMAL]normal')
305
- write('Background: [BRED]red [BGREEN]green [BBLACK]black [NORMAL]normal')
306
-
307
- write('Foreground: [BBLUE:blue] [RED:red] normal')
308
-
309
- write('Bright foreground: [RED_B]red [GREEN_B]green [BLACK_B]black [NORMAL]normal')
310
- write('Bright background: [BRED_B]red [BGREEN_B]green [BBLACK_B]black [NORMAL]normal')
311
-
312
- write('Foreground: [BBLUE:blue_B] [RED:red_B] normal')
313
-
314
- print()
315
-
316
- write('[NORMAL:Normal text]')
317
- write('[FAINT:Faint text]')
318
- write('[ITALIC:Italic text]')
319
- write('[UNDERSCORE:Underscored text]')
320
- write('[BLINK:Blinking text]')
321
- write('[REVERSE:Reverse text]')
322
- write('[STRIKE:Strikethrough text]')
323
-
324
- print()
325
-
326
- error('Error message (nothing should be output after this)', status=0)
327
-
328
- write('This message should not appear')
@@ -79,19 +79,21 @@ class CursesDircolors:
79
79
  """ Given a set of attributes return the equivalent curses colour pair,
80
80
  creating a new one if a matching one doesn't already exsit """
81
81
 
82
- # TODO: Take account of attributes as well as colours
83
-
84
82
  colours = [attr['fore'], attr['back']]
85
83
 
86
84
  # Get an existing colour pair that uses the same colours or create
87
- # a new one if one doesn't exist
85
+ # a new one if one doesn't exist. If a pair slot is already allocated
86
+ # but the curses subsystem cannot allocate (e.g., no colours), skip init.
88
87
 
89
88
  if colours in self.colour_pairs:
90
89
  pair_index = self.colour_pairs.index(colours) + self.reserved
91
90
  else:
92
91
  pair_index = len(self.colour_pairs) + self.reserved
93
92
  self.colour_pairs.append(colours)
94
- curses.init_pair(pair_index, attr['fore'], attr['back'])
93
+ try:
94
+ curses.init_pair(pair_index, attr['fore'], attr['back'])
95
+ except curses.error:
96
+ pass
95
97
 
96
98
  return pair_index
97
99
 
@@ -131,8 +131,10 @@ class Dircolors:
131
131
  Returns a boolean indicating whether any data was loaded.
132
132
  The current database will always be cleared. """
133
133
  self.clear()
134
+ need_close = False
134
135
  if isinstance(database, str):
135
136
  file = open(database, 'r')
137
+ need_close = True
136
138
  elif isinstance(database, TextIOBase):
137
139
  file = database
138
140
  else:
@@ -157,6 +159,8 @@ class Dircolors:
157
159
  continue # ignore TERM directives
158
160
  elif key in _CODE_MAP:
159
161
  self._codes[_CODE_MAP[key]] = val
162
+ elif key.startswith('*.'):
163
+ self._extensions[key[1:]] = val
160
164
  elif key.startswith('.'):
161
165
  self._extensions[key] = val
162
166
  elif strict:
@@ -167,7 +171,8 @@ class Dircolors:
167
171
  self._loaded = True
168
172
  return self._loaded
169
173
  finally:
170
- file.close()
174
+ if need_close:
175
+ file.close()
171
176
 
172
177
  def load_defaults(self):
173
178
  """ Load the default database. """
@@ -35,8 +35,9 @@ def instances(all=False):
35
35
  try:
36
36
  process = subprocess.run(cmd, capture_output=True, check=True, text=True)
37
37
 
38
- for result in process.stdout:
39
- instances_list.append(result)
38
+ for result in process.stdout.splitlines():
39
+ if result:
40
+ instances_list.append(result)
40
41
 
41
42
  except subprocess.CalledProcessError as exc:
42
43
  raise DockerError(exc)
@@ -48,12 +49,17 @@ def instances(all=False):
48
49
  def stop(instance, force=False):
49
50
  """ Stop the specified Docker instance """
50
51
 
51
- # TODO: force option not implemented
52
+ cmd = ['docker', 'stop']
53
+
54
+ if force:
55
+ cmd.append('--force')
56
+
57
+ cmd.append(instance)
52
58
 
53
59
  try:
54
- subprocess.run(['docker', 'stop', instance], check=True, capture_output=False)
60
+ subprocess.run(cmd, check=True, capture_output=False)
55
61
 
56
- except suprocess.CalledProcessError as exc:
62
+ except subprocess.CalledProcessError as exc:
57
63
  raise DockerError(exc)
58
64
 
59
65
  ################################################################################
@@ -80,10 +86,11 @@ def images():
80
86
  """ Return a list of all current Docker images """
81
87
 
82
88
  try:
83
- process = subprocess.run(['docker', 'images', '-q'], capture_output=True, check=True)
89
+ process = subprocess.run(['docker', 'images', '-q'], capture_output=True, check=True, text=True)
84
90
 
85
- for result in process:
86
- yield result
91
+ for result in process.stdout.splitlines():
92
+ if result:
93
+ yield result
87
94
 
88
95
  except subprocess.CalledProcessError as exc:
89
96
  raise DockerError(exc)
@@ -19,7 +19,9 @@ def is_binary_file(filename):
19
19
  """ Return True if there is a strong likelihood that the specified file
20
20
  is binary. """
21
21
 
22
- return file_type(filename, mime=True).endswith('binary')
22
+ filetype = file_type(filename, mime=True)
23
+
24
+ return filetype.endswith('binary') if filetype else False
23
25
 
24
26
  ################################################################################
25
27
 
@@ -49,7 +51,7 @@ def format_size(size, always_suffix=False):
49
51
  """ Convert a memory/disk size into appropriately-scaled units in bytes,
50
52
  MiB, GiB, TiB as a string """
51
53
 
52
- # Keep all the maths positive
54
+ # Keep all the maths positive
53
55
 
54
56
  if size < 0:
55
57
  size = -size
skilleter_modules/git.py CHANGED
@@ -933,7 +933,7 @@ def config_rm(section, key, source=LOCAL, path=None):
933
933
 
934
934
  ################################################################################
935
935
 
936
- def ref(fields=('objectname'), sort=None, remotes=False, path=None):
936
+ def ref(fields=('objectname',), sort=None, remotes=False, path=None):
937
937
  """ Wrapper for git for-each-ref """
938
938
 
939
939
  cmd = ['for-each-ref']
@@ -958,7 +958,7 @@ def ref(fields=('objectname'), sort=None, remotes=False, path=None):
958
958
  def branches(all=False, path=None, remote=False):
959
959
  """ Return a list of all the branches in the current repo """
960
960
 
961
- cmd = ['branch', '--format=%(refname:short)','--list']
961
+ cmd = ['branch', '--format=%(refname:short)', '--list']
962
962
 
963
963
  if all:
964
964
  cmd.append('--all')
skilleter_modules/path.py CHANGED
@@ -32,7 +32,10 @@ def is_subdirectory(root_path, sub_path):
32
32
  logging.debug('root path: %s', abs_root_path)
33
33
  logging.debug('sub path : %s', abs_sub_path)
34
34
 
35
- return abs_sub_path.startswith('%s/' % abs_root_path)
35
+ common = os.path.commonpath([abs_root_path, abs_sub_path])
36
+
37
+ # Require a strict subdirectory: common path matches root and paths differ
38
+ return common == abs_root_path and abs_sub_path != abs_root_path
36
39
 
37
40
  ################################################################################
38
41
 
skilleter_modules/run.py CHANGED
@@ -231,18 +231,19 @@ def _process(command,
231
231
 
232
232
  # Wait until the command terminates (and set the returncode)
233
233
 
234
+ cmd.wait()
235
+
234
236
  if stdout_thread:
235
237
  stdout_thread.join()
236
238
 
237
239
  if stderr_thread:
238
240
  stderr_thread.join()
239
241
 
240
- cmd.wait()
241
-
242
242
  # If the command failed, raise an exception
243
243
 
244
244
  if cmd.returncode:
245
- raise RunError('\n'.join(stderr_data) if stderr_data else 'Error %d running "%s"' % (cmd.returncode, ' '.join(command)))
245
+ raise RunError('\n'.join(stderr_data) if stderr_data else 'Error %d running "%s"' % (cmd.returncode, ' '.join(command)),
246
+ status=cmd.returncode)
246
247
 
247
248
  # Return status, stdout, stderr (the latter 2 may be empty if we did not capture data).
248
249
 
skilleter_modules/tidy.py CHANGED
@@ -57,7 +57,7 @@ RE_TIME = [
57
57
  {'regex': re.compile(r'[0-9]{4}-[0-9]{2}-[0-9]{2}'), 'replace': '{DATE}'},
58
58
  {'regex': re.compile(r'[0-9]{2}-[0-9]{2}-[0-9]{4}'), 'replace': '{DATE}'},
59
59
 
60
- {'regex': re.compile(r'[0-9]([.][0-9]*)*\s*(second[s]?)'), 'replace': '{ELAPSED}'},
60
+ {'regex': re.compile(r'[0-9]+([.][0-9]*)*\s*(second[s]?)'), 'replace': '{ELAPSED}'},
61
61
 
62
62
  {'find': '{DATE} {TIME}', 'replace': '{DATE+TIME}'},
63
63
  {'regex': re.compile(r'[0-9]+m *[0-9]+s'), 'replace': '{ELAPSED}'},
@@ -83,7 +83,7 @@ RE_AWS = \
83
83
  {'regex': re.compile(r'vol-0[0-9a-f]{16}'), 'replace': '{AMI-VOL}'},
84
84
  {'regex': re.compile(r'sir-[0-9a-z]{8}'), 'replace': '{SPOT-INSTANCE}'},
85
85
  {'regex': re.compile(r'i-0[0-9a-f]{16}'), 'replace': '{EC2-ID}'},
86
- {'regex': re.compile(r'request id: [0-0a-f]{8}-[0-0a-f]{4}-[0-0a-f]{4}-[0-0a-f]{4}-[0-0a-f]{12}'),
86
+ {'regex': re.compile(r'request id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'),
87
87
  'replace': 'request id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'},
88
88
  ]
89
89
 
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skilleter_modules
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Modules used by my various Python projects (and hopefully useful to other people)
5
5
  Author-email: John Skilleter <john@skilleter.org.uk>
6
6
  Project-URL: Home, https://skilleter.org.uk
7
+ Project-URL: Repository, https://gitlab.com/skilleter/skilleter-modules
8
+ Project-URL: Issues, https://gitlab.com/skilleter/skilleter-extras/-/modules
7
9
  Classifier: Programming Language :: Python :: 3
8
10
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
9
11
  Classifier: Operating System :: OS Independent
@@ -0,0 +1,20 @@
1
+ skilleter_modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ skilleter_modules/colour.py,sha256=clZFC64xsNmbWBzVhjTLhIOV4T_QNdqYKoTkH-8FeAs,9492
3
+ skilleter_modules/dc_curses.py,sha256=bWtMMt0SAZEXywIa7inLUTPMHqcclEGniRK1S1g7TKk,8670
4
+ skilleter_modules/dc_defaults.py,sha256=ahcteQvoWZrO5iTU68zkIY1Zex6iX5uR5ubwI4CCYBk,6170
5
+ skilleter_modules/dc_util.py,sha256=Df73imXhHx3HzcPHiRcHAoea0e3HURdLcrolUsMhOFs,1783
6
+ skilleter_modules/dircolors.py,sha256=p_rCka-Fnjyu38H8qB3RSZogOkmkbHWYrLbtSWIHcfs,12449
7
+ skilleter_modules/docker.py,sha256=VKcV1aR7X7Yq4ml7zIamwAD05w73Z_2D8yW9MxcNXaE,2923
8
+ skilleter_modules/files.py,sha256=HLrNcUdnpWO-1GEZiDochpVdDKto4G1ukmk4Mszdy7o,4752
9
+ skilleter_modules/git.py,sha256=1rXwhvxHQuIMFqBTRxc1OYOgfcpcIGiWRl1HWRU6x4k,43300
10
+ skilleter_modules/gitlab.py,sha256=uXAF918xnPk6qQyiwPQDbMZfqtJzhiRqDS7yEtJEIAg,6079
11
+ skilleter_modules/path.py,sha256=sk_v9wlRTqZybyl6muAM5yL93ni2LTM0pPj1tyqcaHs,4886
12
+ skilleter_modules/popup.py,sha256=TY9rpj4q8uZxerSt641LGUTy0TZgUjgfEX-CkRMuyek,2540
13
+ skilleter_modules/run.py,sha256=mXzf1kJxNIKSYVT6khJ8s5RiA2cVqkoOFk0P6QPIBY0,10631
14
+ skilleter_modules/tidy.py,sha256=AN_3CRHd8HyWeXlNtd0gZyaA0UJfO4k3IvC5fszeUtk,5972
15
+ skilleter_modules/venv_template.py,sha256=ZfUvi8qFNGrk7J030Zy57xjwMtfIArJyqa-MqafyjVk,1016
16
+ skilleter_modules-0.0.5.dist-info/licenses/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
17
+ skilleter_modules-0.0.5.dist-info/METADATA,sha256=nz9T-iIRzZB9-nroS7WlGwR-kyAbPhyHzoeXkY_YVSw,1824
18
+ skilleter_modules-0.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ skilleter_modules-0.0.5.dist-info/top_level.txt,sha256=DMa0AkGOjoDHiHDG6Nw1jtdUylaMm5WkF6uODT9yxJc,18
20
+ skilleter_modules-0.0.5.dist-info/RECORD,,
@@ -1,593 +0,0 @@
1
- ################################################################################
2
- """ Pane class for tfm """
3
- ################################################################################
4
-
5
- import sys
6
- import os
7
- import curses
8
- import fnmatch
9
- import stat
10
- import glob
11
- import time
12
- import threading
13
-
14
- from enum import IntEnum
15
-
16
- if sys.platform == 'linux':
17
- import inotify.adapters
18
-
19
- from . import dc_curses
20
- from . import path
21
- from . import popup
22
-
23
- ################################################################################
24
-
25
- class SortOrder(IntEnum):
26
- """ Sort order for filename list """
27
-
28
- FILENAME = 0
29
- EXTENSION = 1
30
- MODIFIED_DATE = 2
31
- SIZE = 3
32
- NUM_SORTS = 4
33
-
34
- SORT_TYPE = ('filename', 'extension', 'modified date', 'size')
35
-
36
- ################################################################################
37
-
38
- def inotify_wait(self):
39
- """Thread to wait for inotify events and post an event to the queue if there
40
- any create/delete/modify events in the current directory.
41
- Sends no more than 1 update per second to avoid drowning the recipient."""
42
-
43
- while True:
44
- trigger = False
45
- for event in self.ino.event_gen(yield_nones=False, timeout_s=1):
46
- (_, events, path, _) = event
47
-
48
- if path == self.current_dir and ('IN_CREATE' in events or 'IN_DELETE' in events or 'IN_MODIFY' in events):
49
- trigger = True
50
-
51
- if trigger:
52
- self.event_queue.put(('inotify', self.index))
53
-
54
- ################################################################################
55
-
56
- class Pane():
57
- """ Class for a file manager pane """
58
-
59
- def __init__(self, index, num_panes, colours, event_queue):
60
- # Create window for the pane (dummy size and position initially)
61
-
62
- self.screen = curses.newwin(1, 1, 0, 0)
63
-
64
- self.index = index
65
-
66
- self.current_dir = ''
67
-
68
- self.ino = inotify.adapters.Inotify() if sys.platform == 'linux' else None
69
-
70
- self.set_current_dir(os.getcwd())
71
-
72
- self.event_queue = event_queue
73
-
74
- if sys.platform == 'linux':
75
- inotify_thread = threading.Thread(target=inotify_wait, args=(self,), daemon=True)
76
- inotify_thread.start()
77
-
78
- # Default sort order
79
-
80
- self.sort_order = SortOrder.FILENAME
81
- self.reverse_sort = False
82
-
83
- # Set the attributes of the current review (some are initialised
84
- # when the screen is drawn)
85
-
86
- # Index of the current file in the filtered_file_indices
87
-
88
- self.current = 0
89
-
90
- self.offset = 0
91
- self.num_panes = num_panes
92
- self.colours = colours
93
-
94
- self.searchstring = None
95
-
96
- self.height = self.width = -1
97
- self.file_list_y = 1
98
- self.file_list_h = -1
99
-
100
- # File list is a list of the files in the current directory
101
-
102
- self.file_list = []
103
-
104
- # Filtered file list is a list of the indices in file_list of the visible files
105
- # in the current directory
106
-
107
- self.filtered_file_indices = []
108
-
109
- # Set of the names of currently-tagged files
110
-
111
- self.tagged_set = set()
112
-
113
- self.in_filter = self.out_filter = None
114
- self.hide_hidden_filter = True
115
-
116
- self.file_display_fields = ['size', 'mtime']
117
-
118
- # Set up dircolor highlighting
119
-
120
- self.dircolours = dc_curses.CursesDircolors(reserved=self.colours['reserved_colours'])
121
-
122
- # Generate the list of files to be shown (takes filtering into account)
123
-
124
- self.update_files()
125
-
126
- ################################################################################
127
-
128
- def sort_file_list(self):
129
- """ Sort the file list according to the current sort order """
130
-
131
- if self.sort_order == SortOrder.FILENAME:
132
- self.file_list.sort(reverse=self.reverse_sort, key=lambda entry: (not entry['isdir'], os.path.basename(entry['name'])))
133
- elif self.sort_order == SortOrder.EXTENSION:
134
- self.file_list.sort(reverse=self.reverse_sort, key=lambda entry: (not entry['isdir'], entry['name'].split('.')[-1]))
135
- elif self.sort_order == SortOrder.MODIFIED_DATE:
136
- self.file_list.sort(reverse=self.reverse_sort, key=lambda entry: (not entry['isdir'], entry['mtime']))
137
- elif self.sort_order == SortOrder.SIZE:
138
- self.file_list.sort(reverse=self.reverse_sort, key=lambda entry: (not entry['isdir'], entry['size']))
139
-
140
- ################################################################################
141
-
142
- def update_files(self):
143
- """ Get the list of files
144
- """
145
-
146
- def file_stats(filename):
147
- """ Get the stats for a file """
148
-
149
- filestat = os.stat(filename, follow_symlinks=False)
150
-
151
- info = {'name': filename,
152
- 'mode': filestat.st_mode,
153
- 'uid': filestat.st_uid,
154
- 'gid': filestat.st_gid,
155
- 'size': filestat.st_size,
156
- 'atime': filestat.st_atime,
157
- 'mtime': filestat.st_mtime,
158
- 'ctime': filestat.st_ctime,
159
- 'isdir': stat.S_ISDIR(filestat.st_mode)}
160
-
161
- return info
162
-
163
- # Rebuild the file list
164
-
165
- self.file_list = []
166
- for filename in glob.glob(os.path.join(self.current_dir, '*')) + glob.glob(os.path.join(self.current_dir, '.*')):
167
- self.file_list.append(file_stats(filename))
168
-
169
- # Update the tagged file list to contain only current files
170
-
171
- self.tagged_set = {entry['name'] for entry in self.file_list if entry['name'] in self.tagged_set}
172
-
173
- # Optionally add '..' as an entry
174
-
175
- if self.current_dir != '/':
176
- self.file_list.append(file_stats('..'))
177
-
178
- self.sort_file_list()
179
- self.update_file_list()
180
-
181
- ################################################################################
182
-
183
- def update_file_list(self):
184
- """ Generate the file list from the list of current files with filtering
185
- applied if enabled """
186
-
187
- self.sort_file_list()
188
-
189
- if self.active_filters():
190
- self.filtered_file_indices = [i for i, entry in enumerate(self.file_list) if not self.filtered(entry)]
191
- else:
192
- self.filtered_file_indices = range(len(self.file_list))
193
-
194
- ################################################################################
195
-
196
- def active_filters(self):
197
- """ Return true if any filters are active """
198
-
199
- return self.out_filter or \
200
- self.in_filter or \
201
- self.hide_hidden_filter
202
-
203
- ################################################################################
204
-
205
- def filtered(self, entry):
206
- """ Return True if an entry is hidden by one or more filters """
207
-
208
- result = False
209
-
210
- if self.out_filter and fnmatch.fnmatch(entry['name'], self.out_filter):
211
- result = True
212
-
213
- elif self.in_filter and not fnmatch.fnmatch(entry['name'], self.in_filter):
214
- result = True
215
-
216
- elif self.hide_hidden_filter:
217
- base_name = os.path.basename(entry['name'])
218
- if base_name[0] == '.' and base_name != '..':
219
- result = True
220
-
221
- return result
222
-
223
- ################################################################################
224
-
225
- def constrain_display_parameters(self):
226
- """ Ensure that the current display parameters are within range - easier
227
- to do it in one place for all of them than check individually whenever we
228
- change any of them """
229
-
230
- self.current = max(min(self.current, len(self.filtered_file_indices) - 1), 0)
231
- self.offset = min(len(self.filtered_file_indices) - 1, max(0, self.offset))
232
-
233
- # Keep the current entry on-screen
234
-
235
- if self.current >= self.offset + self.height - 2:
236
- self.offset = self.current
237
- elif self.current < self.offset:
238
- self.offset = self.current
239
-
240
- ################################################################################
241
-
242
- def file_info_display(self, filename):
243
- """ Extract the additional file info fields displayed to the right
244
- of the filename """
245
-
246
- data = []
247
- for field in self.file_display_fields:
248
- if field == 'name':
249
- data.append(filename['name'])
250
- elif field in ('atime', 'mtime', 'ctime'):
251
- data.append(time.strftime('%x %X', time.gmtime(filename[field])))
252
- elif field == 'uid':
253
- pass
254
- elif field == 'gid':
255
- pass
256
- elif field == 'mode':
257
- pass
258
- elif field == 'size':
259
- data.append(str(filename[field]))
260
-
261
- return ' '.join(data)
262
-
263
- ################################################################################
264
-
265
- def show_file_list(self, current_pane):
266
- """ Draw the current page of the file list """
267
-
268
- for ypos in range(0, self.file_list_h):
269
-
270
- normal_colour = curses.color_pair(self.colours['normal'])
271
-
272
- if 0 <= self.offset + ypos < len(self.filtered_file_indices):
273
- # Work out what colour to render the file details in
274
-
275
- current_file = self.file_list[self.filtered_file_indices[self.offset + ypos]]
276
-
277
- current = self.offset + ypos == self.current
278
-
279
- # The text to render
280
-
281
- filename = os.path.basename(current_file['name'])
282
-
283
- data = self.file_info_display(current_file)
284
-
285
- name = f'/{filename}' if current_file['isdir'] else filename
286
- name = f'* {name}' if current_file['name'] in self.tagged_set else f' {name}'
287
-
288
- if len(name) > self.width - len(data):
289
- entry = name[:self.width - 3] + '...'
290
- else:
291
- entry = name + ' ' * (self.width - len(name) - len(data)) + data
292
-
293
- file_colour = self.dircolours.get_colour_pair(current_file['name'], current_file['mode'])
294
- else:
295
- filename = entry = None
296
- current = False
297
- file_colour = normal_colour
298
-
299
- # Reverse the colours if this the cursor line
300
-
301
- if current and current_pane:
302
- file_colour |= curses.A_REVERSE
303
- normal_colour |= curses.A_REVERSE
304
-
305
- # Write the prefix, filename, and, if necessary, padding
306
-
307
- self.screen.move(self.file_list_y + ypos, 0)
308
- if entry:
309
- self.screen.addstr(entry, file_colour)
310
- else:
311
- self.screen.clrtoeol()
312
-
313
- # if len(filename) < self.width:
314
- # self.screen.addstr(self.file_list_y + ypos, len(filename), ' ' * (self.width - len(filename)), normal_colour)
315
-
316
- current_dir = path.trimpath(self.current_dir, self.width)
317
-
318
- self.screen.move(0, 0)
319
- self.screen.attron(curses.color_pair(self.colours['status']))
320
- self.screen.addstr(current_dir + ' '*(self.width-len(current_dir)))
321
-
322
- self.screen.refresh()
323
-
324
- if not self.filtered_file_indices:
325
- with popup.PopUp(self.screen, 'All files are hidden - Press \'c\' to clear filters.', self.colours['status']):
326
- pass
327
-
328
- ################################################################################
329
-
330
- def filter_description(self):
331
- """ Return a textual description of the active filters """
332
-
333
- filters = []
334
-
335
- if self.out_filter:
336
- filters.append('filter-out wildcard')
337
-
338
- if self.in_filter:
339
- filters.append('filter-in wildcard')
340
-
341
- return ', '.join(filters)
342
-
343
- ################################################################################
344
-
345
- def clear_filters(self):
346
- """ Clear all filters """
347
-
348
- if self.out_filter or self.in_filter:
349
- self.out_filter = self.in_filter = None
350
- self.update_file_list()
351
-
352
- ################################################################################
353
-
354
- def reload_changes(self):
355
- """ Update the list of files in case something external has
356
- changed it. """
357
-
358
- self.update_files()
359
-
360
- ################################################################################
361
-
362
- def get_current_dir(self):
363
- """ Get the current directory for the pane """
364
-
365
- return self.current_dir
366
-
367
- ################################################################################
368
-
369
- def set_current_dir(self, directory):
370
- """ Set the current directory for the pane """
371
-
372
- if self.current_dir and self.ino:
373
- self.ino.remove_watch(self.current_dir)
374
-
375
- self.current_dir = os.path.normpath(directory)
376
-
377
- if self.ino:
378
- self.ino.add_watch(directory)
379
-
380
- ################################################################################
381
-
382
- def get_current_file(self):
383
- """ Get the current file for the pane """
384
-
385
- return self.file_list[self.filtered_file_indices[self.current]]
386
-
387
- ################################################################################
388
-
389
- def get_tagged_files(self):
390
- """ Get the list of tagged files, or the current file if none are tagged """
391
-
392
- if self.tagged_set:
393
- return [self.file_list[entry] for entry in self.filtered_file_indices if self.file_list[entry]['name'] in self.tagged_set]
394
-
395
- return [self.get_current_file()]
396
-
397
- ################################################################################
398
-
399
- def search_entry(self, searchstring):
400
- """ Search for the next match with the specified search string """
401
-
402
- for i in list(range(self.current + 1, len(self.filtered_file_indices))) + list(range(0, self.current)):
403
- if fnmatch.fnmatch(os.path.basename(self.file_list[self.filtered_file_indices[i]]['name']), searchstring):
404
- self.current = i
405
- break
406
-
407
- ################################################################################
408
-
409
- def search_match(self, searchstring):
410
- """ Search for the first match """
411
-
412
- self.searchstring = searchstring
413
- self.search_next_match()
414
-
415
- ################################################################################
416
-
417
- def search_next_match(self):
418
- """ Search for the next match with the current search string """
419
-
420
- self.search_entry(self.searchstring)
421
-
422
- ################################################################################
423
-
424
- def move_end(self):
425
- """ Move to the end of the file list """
426
-
427
- self.current = len(self.filtered_file_indices) - 1
428
-
429
- ################################################################################
430
-
431
- def move_top(self):
432
- """ Move to the top of the file list """
433
-
434
- self.current = self.offset = 0
435
-
436
- ################################################################################
437
-
438
- def move_to_file(self, filename):
439
- """ Move to the specified file (if it exists) in the current directory
440
- or to the top if not """
441
-
442
- self.current = self.offset = 0
443
- if filename:
444
- self.search_entry(filename)
445
-
446
- ################################################################################
447
-
448
- def move(self, delta):
449
- """ Move up or down the file list """
450
-
451
- self.current += delta
452
-
453
- ################################################################################
454
-
455
- def filter_out(self, filter_out):
456
- """ Set an exclusion filter """
457
-
458
- self.out_filter = filter_out
459
- self.in_filter = None
460
- self.update_file_list()
461
-
462
- ################################################################################
463
-
464
- def filter_in(self, filter_in):
465
- """ Set an inclusion filter """
466
-
467
- self.in_filter = filter_in
468
- self.out_filter = None
469
- self.update_file_list()
470
-
471
- ################################################################################
472
-
473
- def move_page_down(self):
474
- """ Page down """
475
-
476
- pos = self.current - self.offset
477
- self.offset += self.file_list_h - 1
478
- self.current = self.offset + pos
479
-
480
- ################################################################################
481
-
482
- def move_page_up(self):
483
- """ Page up """
484
-
485
- pos = self.current - self.offset
486
- self.offset -= self.file_list_h - 1
487
- self.current = self.offset + pos
488
-
489
- ################################################################################
490
-
491
- def set_sort_order(self, value):
492
- """ Set the sort order """
493
-
494
- self.sort_order = (self.sort_order + value) % SortOrder.NUM_SORTS
495
-
496
- self.update_sort()
497
-
498
- ################################################################################
499
-
500
- def get_sort_order(self):
501
- """ Get the current sort order """
502
-
503
- return self.sort_order
504
-
505
- ################################################################################
506
-
507
- def sort_type_msg(self):
508
- """ Return a textual explanation of the current sort type """
509
-
510
- if self.reverse_sort:
511
- msg = f'Reverse-sorting by {SORT_TYPE[self.sort_order]}'
512
- else:
513
- msg = f'Sorting by {SORT_TYPE[self.sort_order]}'
514
-
515
- return msg
516
-
517
- ################################################################################
518
-
519
- def reverse_sort_order(self):
520
- """ Reverse the sort order """
521
-
522
- self.reverse_sort = not self.reverse_sort
523
- self.update_sort()
524
-
525
- ################################################################################
526
-
527
- def update_sort(self):
528
- """ Update the sort """
529
-
530
- msg = self.sort_type_msg()
531
-
532
- with popup.PopUp(self.screen, msg, self.colours['status']):
533
- self.update_file_list()
534
-
535
- ################################################################################
536
-
537
- def set_pane_coords(self, y, x, height, width):
538
- """ Set the pane height given the pane display area """
539
-
540
- pane_width = width//self.num_panes
541
-
542
- self.height = height
543
- self.file_list_h = height-1
544
- self.width = pane_width-1 # TODO: Why '-1'?
545
- self.screen.resize(height, pane_width)
546
- self.screen.mvwin(y, x + pane_width*self.index)
547
-
548
- ################################################################################
549
-
550
- def tag_current(self):
551
- """ Tag the current entry (unless it is '..') """
552
-
553
- current = self.file_list[self.filtered_file_indices[self.current]]['name']
554
-
555
- if current != '..':
556
- if current in self.tagged_set:
557
- self.tagged_set.remove(current)
558
- else:
559
- self.tagged_set.add(current)
560
-
561
- ################################################################################
562
-
563
- def untag(self, wildcard=None):
564
- """ Tag all, or selected tagged items """
565
-
566
- if wildcard:
567
- remove_tags = set()
568
- for entry in self.tagged_set:
569
- if fnmatch.fnmatch(self.filtered_file_indices[entry], wildcard):
570
- remove_tags.add(entry)
571
-
572
- self.tagged_set -= remove_tags
573
- else:
574
- self.tagged_set = set()
575
-
576
- ################################################################################
577
-
578
- def get_hidden_visibility(self):
579
- """ Return the current state of hidden file visibility """
580
-
581
- return not self.hide_hidden_filter
582
-
583
- ################################################################################
584
-
585
- def set_hidden_visibility(self, state=False):
586
- """ Set the visibility of hidden files """
587
-
588
- self.hide_hidden_filter = not state
589
-
590
- change_txt = 'Hiding' if self.hide_hidden_filter else 'Showing'
591
-
592
- with popup.PopUp(self.screen, f'{change_txt} hidden files', self.colours['status']):
593
- self.update_file_list()
@@ -1,21 +0,0 @@
1
- skilleter_modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- skilleter_modules/colour.py,sha256=8fWsuMvqUrC8IUOj1e4x7xt_4WFAgq_gzcc5CXorydo,11201
3
- skilleter_modules/dc_curses.py,sha256=fuuQPR11zV_akAhygL_cAhVLC5YAgKgowzlITVbETE8,8539
4
- skilleter_modules/dc_defaults.py,sha256=ahcteQvoWZrO5iTU68zkIY1Zex6iX5uR5ubwI4CCYBk,6170
5
- skilleter_modules/dc_util.py,sha256=Df73imXhHx3HzcPHiRcHAoea0e3HURdLcrolUsMhOFs,1783
6
- skilleter_modules/dircolors.py,sha256=1Grt7AZj13GK6IraZVh8ECEZacAfpDC9gx0g9yGW1aU,12266
7
- skilleter_modules/docker.py,sha256=6F6SewGMNk6FV_UICJuEGeyU5bqF-NRN9FHCefKNDNA,2790
8
- skilleter_modules/files.py,sha256=YmmYgUoG3G1ZPrHmTuuTJqedLdVxyVsKvpFllLMVjdc,4703
9
- skilleter_modules/git.py,sha256=_dt3VS6WxsxWBWfFH4yMz5C8LP7luPluqyEZweF1LPM,43298
10
- skilleter_modules/gitlab.py,sha256=uXAF918xnPk6qQyiwPQDbMZfqtJzhiRqDS7yEtJEIAg,6079
11
- skilleter_modules/path.py,sha256=8uM2Q9zFRWv_SaVOX49PeecQXttl7J6lsmBuRXWsXKY,4732
12
- skilleter_modules/popup.py,sha256=TY9rpj4q8uZxerSt641LGUTy0TZgUjgfEX-CkRMuyek,2540
13
- skilleter_modules/run.py,sha256=-b0jGVjHdyZj1nSoErJNLnEmPjzpgDuDGavtKApQdtA,10585
14
- skilleter_modules/tfm_pane.py,sha256=PgdEtoV2-ZnIvOpvbrNkZY3agZxgZmHOAuUC7Wzn4ic,19762
15
- skilleter_modules/tidy.py,sha256=AQ2RawsZJg6WHrgayi_ZptFL9occ7suSdCHbU3P-cys,5971
16
- skilleter_modules/venv_template.py,sha256=ZfUvi8qFNGrk7J030Zy57xjwMtfIArJyqa-MqafyjVk,1016
17
- skilleter_modules-0.0.3.dist-info/licenses/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
18
- skilleter_modules-0.0.3.dist-info/METADATA,sha256=wD0c8lx2lquktgbtCf2PHtJej0Y-XwRF6joU4at7yoY,1675
19
- skilleter_modules-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- skilleter_modules-0.0.3.dist-info/top_level.txt,sha256=DMa0AkGOjoDHiHDG6Nw1jtdUylaMm5WkF6uODT9yxJc,18
21
- skilleter_modules-0.0.3.dist-info/RECORD,,