Package gavo :: Package helpers :: Module filestuff
[frames] | no frames]

Source Code for Module gavo.helpers.filestuff

  1  """ 
  2  File- and directory related helpers for resource utilites. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  from __future__ import print_function 
 12   
 13  import os 
 14  import re 
 15  import warnings 
 16   
 17  from gavo import base 
 18  from gavo import rscdef 
 19  from gavo.base import parsecontext 
20 21 -class Error(base.Error):
22 pass
23 24 25 fnamePat = re.compile(r"([^.]*)(\..*)")
26 -def stingySplitext(fName):
27 """returns name, extension for fName. 28 29 The main difference to os.path.splitext is that the main name is not allowed 30 to contain dots and the extension can contain more than one dot. 31 32 fName is supposed to be a single file name without any path specifier 33 (you might get aways with it if your directores do not contain dots, though). 34 """ 35 mat = fnamePat.match(fName) 36 if mat: 37 return mat.group(1), mat.group(2) 38 else: 39 return fName, ""
40
41 42 -class FileRenamer(object):
43 """is a name mapper for file rename operations and the like. 44 45 Warning: This whole thing more or less pretends there are no 46 symlinks. 47 """
48 - def __init__(self, map, showOnly=False):
49 self.map, self.showOnly = map, showOnly
50 51 @classmethod
52 - def loadFromFile(cls, inF, **kwargs):
53 """returns a name map for whatever is serialized in the file inF. 54 55 The format of fName is line-base, with each line being one of 56 57 - empty -- ignored 58 - beginning with a hash -- ignored 59 - <old> -> <new> -- introducing a map 60 """ 61 map = {} 62 try: 63 for ln in inF: 64 if not ln.strip() or ln.strip().startswith("#"): 65 continue 66 old, new = [s.strip() for s in ln.split("->", 2)] 67 if old in map: 68 raise Error("Two mappings for %s"%old) 69 map[old] = new 70 except ValueError: 71 raise base.ui.logOldExc(Error("Invalid mapping line: %s"%repr(ln))) 72 return cls(map, **kwargs)
73
74 - def getFileMap(self, path):
75 """returns a dictionary old->new of renames within path. 76 """ 77 fileMap = {} 78 for dir, subdirs, fNames in os.walk(path): 79 for fName in fNames: 80 stem, ext = stingySplitext(fName) 81 if stem in self.map: 82 fileMap[os.path.join(dir, fName)] = os.path.join(dir, 83 self.map[stem]+ext) 84 return fileMap
85
86 - def makeRenameProc(self, fileMap):
87 """returns a sequence of (old,new) pairs that, when executed, keep 88 any new from clobbering any existing old. 89 90 The function will raise an Error if there's a cycle in fileMap. fileMap 91 will be destroyed by this procedure 92 """ 93 proc = [] 94 def addOp(src, dest, sources=None): 95 if sources is None: 96 sources = set() 97 if dest in sources: 98 raise Error("Rename cycle involving %s"%sources) 99 if dest in fileMap: 100 sources.add(src) 101 addOp(dest, fileMap.pop(dest), sources) 102 proc.append((src, dest))
103 while fileMap: 104 addOp(*fileMap.popitem()) 105 return proc
106
107 - def renameInPath(self, path):
108 """performs a name map below path. 109 110 The rules are: 111 112 - extensions are ignored -- if we map foo to bar, foo.txt and foo.asc 113 will be renamed bar.txt and foo.txt respectively 114 - the order of statements in the source is irrelevant. However, we try 115 not to clobber anything we've just renamed and will complain about 116 cycles. Also, each file will be renamed not more than once. 117 """ 118 fileMap = self.getFileMap(path) 119 for src, dest in self.makeRenameProc(fileMap): 120 if os.path.exists(dest): 121 raise Error("Request to clobber %s"%repr(dest)) 122 if os.path.exists(src): 123 if self.showOnly: 124 print("%s -> %s"%(src, dest)) 125 else: 126 os.rename(src, dest) 127 else: 128 if not os.path.exists(dest): 129 warnings.warn("Neither source nor dest found in pair %s, %s"%(src, 130 dest))
131
132 - def renameInPaths(self, pathList):
133 for path in pathList: 134 self.renameInPath(path)
135
136 137 -def iterSources(ddId, args=[]):
138 """iterates over the current sources of the data descriptor ddId (which is 139 qualified like rdId#id 140 141 If you pass something nonempty to args, an iterator over its values 142 will be returned. This is for convenient implementation of scripts 143 that work on CL arguments if given, on all files otherwise. 144 """ 145 if args: 146 return iter(args) 147 else: 148 if ddId.count("#")!=1: 149 raise base.ReportableError("iterSources must get a fully qualified id" 150 " (i.e., one with exactly one hash).") 151 dd = parsecontext.resolveCrossId(ddId, rscdef.DataDescriptor) 152 return dd.sources.iterSources()
153