1 #!/usr/bin/env python 2 3 """ 4 Directory context functionality. 5 6 Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from os import makedirs, rename, walk 23 from os.path import abspath, commonprefix, exists, isdir, isfile, join 24 import fnmatch 25 26 # Get the directory with trailing path separator when assessing path prefixes 27 # in order to prevent sibling directory confusion. 28 29 def inside(filename, dirname): 30 31 "Return whether 'filename' is inside 'dirname'." 32 33 dirname = join(dirname, "") 34 return commonprefix((filename, dirname)) == dirname 35 36 def within(filename, dirname): 37 38 "Return the part of 'filename' found within 'dirname'." 39 40 dirname = join(dirname, "") 41 prefix = commonprefix((filename, dirname)) 42 43 if prefix == dirname: 44 return filename[len(prefix):] 45 else: 46 return None 47 48 49 50 class Directory: 51 52 "A directory abstraction." 53 54 def __init__(self, filename): 55 56 "Initialise the abstraction with the given 'filename'." 57 58 self.filename = abspath(filename) 59 60 def ensure(self): 61 62 "Ensure that this directory exists." 63 64 if not exists(self.filename): 65 makedirs(self.filename) 66 67 def get_filename(self, filename): 68 69 """ 70 Return the full path of a file with the given 'filename' found within 71 the directory. The full path is an absolute path. 72 """ 73 74 # Get the absolute path for the combination of directory and filename. 75 76 pathname = abspath(join(self.filename, filename)) 77 78 if inside(pathname, self.filename): 79 return pathname 80 else: 81 raise ValueError, filename 82 83 # File operations acting on relative filenames. 84 85 def _apply(self, fn, filename): 86 87 "Apply 'fn' to the relative 'filename'." 88 89 return fn(self.get_filename(filename)) 90 91 def exists(self, filename): 92 93 "Return whether the relative 'filename' exists within the directory." 94 95 return self._apply(exists, filename) 96 97 def isdir(self, filename): 98 99 "Return whether the relative 'filename' is a directory." 100 101 return self._apply(isdir, filename) 102 103 def isfile(self, filename): 104 105 "Return whether the relative 'filename' is a file." 106 107 return self._apply(isfile, filename) 108 109 def makedirs(self, filename): 110 111 """ 112 Ensure that a directory having the given 'filename' exists by creating 113 it and any directories needed for it to be created. This filename is 114 relative to the directory. 115 """ 116 117 pathname = self.get_filename(filename) 118 119 if not exists(pathname): 120 makedirs(pathname) 121 122 def rename(self, old, new): 123 124 """ 125 Rename the file with the 'old' relative filename to the 'new' relative 126 filename. 127 """ 128 129 rename(self.get_filename(old), self.get_filename(new)) 130 131 def select_files(self, pattern): 132 133 """ 134 Return a list of filenames found within the directory matching 135 'pattern'. These filenames are relative to the directory. 136 """ 137 138 selected = [] 139 140 # Obtain pathnames, directory names and filenames within the directory. 141 142 for dirpath, dirnames, filenames in walk(self.filename): 143 for filename in filenames: 144 145 # Qualify filenames with the directory path. 146 147 pathname = join(dirpath, filename) 148 149 # Obtain the local filename within the directory. 150 151 local_filename = self.within(pathname) 152 153 # Match filenames supporting the pattern. 154 155 if local_filename and fnmatch.fnmatch(local_filename, pattern): 156 selected.append(local_filename) 157 158 return selected 159 160 # File operations involving complete filenames. 161 162 def within(self, filename): 163 164 """ 165 Return the given complete 'filename' translated to be relative to this 166 directory, or return None if the filename describes a location outside 167 the directory. 168 """ 169 170 return within(filename, self.filename) 171 172 # vim: tabstop=4 expandtab shiftwidth=4