-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathsam_ls.py
364 lines (306 loc) · 15.5 KB
/
sam_ls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
#!/usr/bin/env python
# coding: utf-8
# PYTHON_ARGCOMPLETE_OK
import os
from datetime import date
import argparse
from clint.textui import colored, puts, indent
# parser of sequence
from pySequenceParser import sequenceParser
# sam common functions
from common import samUtils
class Sam_ls(samUtils.Sam):
"""
Class which represents the sam_ls operation.
"""
def __init__(self):
samUtils.Sam.__init__(self)
self.command = 'ls'
self.help = 'to list sequences (and other files)'
self.description = str(colored.green('''
List information about sequences, files and folders.
List the current directory by default.
'''))
self._itemPrinted = [] # name of Items already printed
self._sequenceExploded = [] # name of Sequences already exploded
def fillParser(self, parser):
def level_type(level):
"""
Check constrains of 'level' argument.
"""
level = int(level)
if level < 0:
raise argparse.ArgumentTypeError("Minimum level is 0")
return level
# Arguments
parser.add_argument('inputs', nargs='*', action='store', help='list of files/sequences/directories to analyse').completer = samUtils.sequenceParserCompleter
# Options
parser.add_argument('-a', '--all', dest='all', action='store_true', help='do not ignore entries starting with .')
parser.add_argument('-d', '--directories', dest='directories', action='store_true', help='display only directories')
parser.add_argument('-s', '--sequences', dest='sequences', action='store_true', help='display only sequences')
parser.add_argument('-f', '--files', dest='files', action='store_true', help='display only files')
parser.add_argument('-e', '--expression', dest='expression', action='append', default=[], help='use a specific pattern, ex: "*.jpg", "*.png" (do not forget the quotes to avoid the shell interpretation!)')
parser.add_argument('-l', '--long-listing', dest='longListing', action='store_true', help='use a long listing format (display in this order: type | permissions | owner | group | last update | minSize | maxSize | totalSize | name)')
parser.add_argument('--format', dest='format', choices=['default', 'nuke', 'rv'], default='default', help='specify formatting of the sequence padding')
parser.add_argument('-R', '--recursive', dest='recursive', action='store_true', help='handle directories and their content recursively')
parser.add_argument('-L', '--level', dest='level', type=level_type, help='max display depth of the directory tree (without formatting if 0)')
parser.add_argument('--absolute-path', dest='absolutePath', action='store_true', help='display the absolute path of each object')
parser.add_argument('--relative-path', dest='relativePath', action='store_true', help='display the relative path of each object')
parser.add_argument('--detect-negative', dest='detectNegative', action='store_true', help='detect negative numbers instead of detecting "-" as a non-digit character')
parser.add_argument('--detect-without-holes', dest='detectWithoutHoles', action='store_true', help='detect sequences without holes')
parser.add_argument('--explode-sequences', dest='explodeSequences', action='store_true', default=False, help='explode sequences as several sequences without holes')
parser.add_argument('--script', dest='script', action='store_true', default=False, help='Format the output such as it could be dump in a file and be used as a script')
parser.add_argument('-v', '--verbose', dest='verbose', action=samUtils.SamSetVerboseAction, default=2, help='verbose level (0/fatal, 1/error, 2/warn(by default), 3/info, 4(or upper)/debug)')
def _isAlreadyPrinted(self, item):
"""
Return if the given item has already been printed.
@see _printItem
"""
if item.getAbsoluteFilepath() in self._itemPrinted:
return True
return False
def _needToPrintCurrentFolder(self, args):
"""
Return if the current folder expects to be printed before its content.
See behavior of 'ls' UNIX command with several arguments.
"""
return len(args.inputs) > 1 and not args.script and not args.recursive
def _printItem(self, item, args, level):
"""
Print the item depending on the command line options.
"""
itemType = item.getType()
filePath = ''
detailed = ''
detailedSequence = ''
# sam-ls --explode-sequences
sequenceExploded = False
if args.explodeSequences and itemType == sequenceParser.eTypeSequence:
sequence = item.getSequence()
for frameRange in sequence.getFrameRanges():
# for each frame range, print a new item as sequence
subSequence = sequenceParser.Sequence(sequence.getPrefix(), sequence.getFixedPadding(), sequence.getMaxPadding(), sequence.getSuffix(), frameRange.first, frameRange.last, frameRange.step)
if subSequence.__str__() not in self._sequenceExploded:
self._sequenceExploded.append(subSequence.__str__())
sequenceExploded = True
self._printItem(sequenceParser.Item(subSequence, item.getFolder()), args, level)
# to skip recursivity
if sequenceExploded:
return
# sam-ls -l
if args.longListing:
# type - date - size
characterFromType = 'a'
if itemType == sequenceParser.eTypeUndefined:
characterFromType = '?'
elif itemType == sequenceParser.eTypeFolder:
characterFromType = 'd'
elif itemType == sequenceParser.eTypeFile:
characterFromType = 'f'
elif itemType == sequenceParser.eTypeSequence:
characterFromType = 's'
elif itemType == sequenceParser.eTypeLink:
characterFromType = 'l'
# type - permissions - user - group - lastUpdate - size
itemStat = sequenceParser.ItemStat(item)
permissions = ''
permissions += 'r' if itemStat.ownerCanRead else '-'
permissions += 'w' if itemStat.ownerCanWrite else '-'
permissions += 'x' if itemStat.ownerCanExecute else '-'
permissions += 'r' if itemStat.groupCanRead else '-'
permissions += 'w' if itemStat.groupCanWrite else '-'
permissions += 'x' if itemStat.groupCanExecute else '-'
permissions += 'r' if itemStat.otherCanRead else '-'
permissions += 'w' if itemStat.otherCanWrite else '-'
permissions += 'x' if itemStat.otherCanExecute else '-'
lastUpdate = date.fromtimestamp(itemStat.modificationTime).strftime('%d/%m/%y')
minSize = samUtils.getReadableSize(itemStat.minSize) if itemStat.minSize != itemStat.size else '-'
maxSize = samUtils.getReadableSize(itemStat.maxSize) if itemStat.maxSize != itemStat.size else '-'
detailed = '{:1}{:9}'.format(characterFromType, permissions)
detailed += ' {:8} {:8} {:8}'.format(itemStat.getUserName(), itemStat.getGroupName(), lastUpdate)
detailed += ' {:6} {:6} {:6} '.format(minSize, maxSize, samUtils.getReadableSize(itemStat.size))
# only for sequences: [ begin : end ] nbFiles - nbMissingFiles
if itemType == sequenceParser.eTypeSequence:
sequence = item.getSequence()
detailedSequence = ' [{first}:{last}] {nbFiles} files'.format(first=sequence.getFirstTime(), last=sequence.getLastTime(), nbFiles=sequence.getNbFiles())
nbHoles = (sequence.getLastTime() - sequence.getFirstTime() + 1) - sequence.getNbFiles()
if nbHoles:
detailedSequence += ' - {nbHoles} missing files'.format(nbHoles=nbHoles)
# sam-ls --absolute-path
if args.absolutePath:
filePath += os.path.abspath(item.getFolder())
# sam-ls --relative-path
if args.relativePath:
filePath += item.getFolder()
# filename
filename = item.getFilename()
# sam-ls --format
if itemType == sequenceParser.eTypeSequence:
filename = samUtils.getSequenceNameWithFormatting(item.getSequence(), args.format)
# colors
if itemType == sequenceParser.eTypeFolder:
# blue is not visible without bold
filePath = colored.blue(os.path.join(filePath, filename), bold=True)
elif itemType == sequenceParser.eTypeFile:
filePath = colored.green(os.path.join(filePath, filename))
elif itemType == sequenceParser.eTypeSequence:
# magenta is not visible without bold
filePath = colored.magenta(os.path.join(filePath, filename), bold=True)
elif itemType == sequenceParser.eTypeLink:
filePath = colored.cyan(os.path.join(filePath, filename))
else:
filePath = colored.red(os.path.join(filePath, filename))
# sam-ls -R / sam-ls -L / sam-ls --script
indentTree = ''
if args.recursive and level != 0 and not args.script:
indentTree += '| ' * (level - 1)
indentTree += '|__ '
# display
toPrint = detailed + filePath
if not args.script:
toPrint += detailedSequence
# if first level or no tree formatting
if level == 0 or args.script:
puts(toPrint.format())
else:
with indent(level, quote=indentTree):
puts(toPrint.format())
self._itemPrinted.append(item.getAbsoluteFilepath())
def _printItems(self, items, args, detectionMethod, filters, level=0):
"""
For each items, check if it should be printed, depending on the command line options.
Return if the operation was a success or not.
"""
error = 0
# sam-ls --script
if self._needToPrintCurrentFolder(args):
puts(items[0].getFolder() + ':')
for item in sorted(items):
itemType = item.getType()
toPrint = False
# sam-ls default case: print all items
if not args.directories and not args.files and not args.sequences:
toPrint = True
else:
# sam-ls -d
if args.directories and itemType == sequenceParser.eTypeFolder:
toPrint = True
# sam-ls -f
elif args.files and itemType == sequenceParser.eTypeFile:
toPrint = True
# sam-ls -s
elif args.sequences and itemType == sequenceParser.eTypeSequence:
toPrint = True
# skip item already printed
if toPrint and self._isAlreadyPrinted(item):
toPrint = False
# print current item
if toPrint:
self._printItem(item, args, level)
# sam-ls -R
if args.recursive and itemType == sequenceParser.eTypeFolder:
# sam-ls -L
if args.level and args.level <= level:
continue
try:
newFolder = os.path.join(item.getFolder(), item.getFilename())
self.logger.debug('Browse in "' + newFolder + '" with the following filters: ' + str(filters))
newItems = sequenceParser.browse(newFolder, detectionMethod, filters)
self._printItems(newItems, args, detectionMethod, filters, level + 1)
except IOError as e:
self.logger.error(e)
error = 1
# sam-ls --script
if self._needToPrintCurrentFolder(args):
puts(newline=True)
return error
def run(self, parser):
"""
Process the list operation.
"""
# Parse command-line
args = parser.parse_args()
# sam-ls --absolute-path --relative-path
if args.absolutePath and args.relativePath:
self._displayCommandLineHelp(parser)
exit(1)
# Set sam log level
self.setLogLevel(args.verbose)
class InputToBrowse(object):
"""
Represents an input to browse: a path and a list of filters.
"""
def __init__(self, inputPath):
self.inputPath = inputPath
self.filters = []
def addFilter(self, filterToAdd):
self.filters.append(filterToAdd)
# translate user inputs to a list of InputToBrowse
inputsToBrowse = []
for inputPath in args.inputs:
# if the input is a directory, add it and continue
if os.path.isdir(inputPath):
inputsToBrowse.append(InputToBrowse(inputPath))
continue
# else split the input to a path and a filename
subPath = os.path.dirname(inputPath)
if not subPath:
subPath = '.'
filename = os.path.basename(inputPath)
# add the path and the filename as an expression
inputToBrowse = InputToBrowse(subPath)
inputToBrowse.addFilter(filename)
inputsToBrowse.append(inputToBrowse)
# if no user input, will browse in the current working directory
if not inputsToBrowse:
inputsToBrowse.append(InputToBrowse('.'))
# sam-ls -a
detectionMethod = sequenceParser.eDetectionDefault
if args.all:
detectionMethod = sequenceParser.eDetectionDefaultWithDotFile
# sam-ls --detect-negative
if args.detectNegative:
detectionMethod = detectionMethod | sequenceParser.eDetectionNegative
# sam-ls --detect-without-holes
if args.detectWithoutHoles:
detectionMethod = detectionMethod | sequenceParser.eDetectionSequenceWithoutHoles
# sam-ls -e
for expression in args.expression:
for inputToBrowse in inputsToBrowse:
inputToBrowse.addFilter(expression)
# for each input to browse, print the finding items
error = 0
for inputToBrowse in inputsToBrowse:
items = []
inputPath = inputToBrowse.inputPath
filters = inputToBrowse.filters
try:
self.logger.debug('Browse in "' + inputPath + '" with the following filters: ' + str(filters))
items = sequenceParser.browse(inputPath, detectionMethod, filters)
except Exception as e:
self.logger.error(e)
error = 1
finally:
# if there are no items found and the user indicate a name interpreted as filter
if not error and not len(items) and len(filters) > 0:
self.logger.error('Cannot access "' + inputPath + '" with the following filters: ' + str(filters) + ': No such file or directory.')
error = 1
else:
err = self._printItems(items, args, detectionMethod, filters)
if err:
error = err
exit(error)
if __name__ == '__main__':
# Create the tool
tool = Sam_ls()
# Create command-line interface
parser = argparse.ArgumentParser(
prog='sam-'+tool.command,
description=tool.description,
formatter_class=argparse.RawTextHelpFormatter,
)
tool.fillParser(parser)
# Activate completion
samUtils.doCompletion(parser)
# Run the command
tool.run(parser)