2 # Copyright 2011-2013 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
10 from portage.util._argparse import ArgumentParser
12 CONTENT_ENCODING = "utf_8"
22 except AttributeError:
23 # magic module seems to be broken
26 class IsTextFile(object):
30 self._call = self._is_text_magic
31 self._m = magic.open(magic.MIME_TYPE)
34 self._call = self._is_text_encoding
35 self._encoding = CONTENT_ENCODING
37 def __call__(self, filename):
39 Returns True if the given file is a text file, and False otherwise.
41 return self._call(filename)
43 def _is_text_magic(self, filename):
44 mime_type = self._m.file(filename)
45 if isinstance(mime_type, bytes):
46 mime_type = mime_type.decode('ascii', 'replace')
47 return mime_type.startswith("text/")
49 def _is_text_encoding(self, filename):
51 for line in io.open(filename, mode='r', encoding=self._encoding):
53 except UnicodeDecodeError:
57 def chpath_inplace(filename, is_text_file, old, new):
59 Returns True if any modifications were made, and False otherwise.
63 orig_stat = os.lstat(filename)
65 f = io.open(filename, buffering=0, mode='r+b')
68 orig_mode = stat.S_IMODE(os.lstat(filename).st_mode)
70 sys.stderr.write("%s: %s\n" % (e, filename))
72 temp_mode = 0o200 | orig_mode
73 os.chmod(filename, temp_mode)
75 f = io.open(filename, buffering=0, mode='r+b')
77 os.chmod(filename, orig_mode)
81 matched_byte_count = 0
88 if in_byte == old[matched_byte_count]:
89 matched_byte_count += 1
90 if matched_byte_count == len_old:
92 matched_byte_count = 0
93 end_position = f.tell()
94 start_position = end_position - len_old
96 # search backwards for leading slashes written by
97 # a previous invocation of this tool
98 num_to_write = len_old
99 f.seek(start_position - 1)
101 if f.read(1) != b'/':
106 # pad with as many leading slashes as necessary
107 while num_to_write > len_new:
113 f.seek(start_position)
118 f.seek(start_position + len_new)
119 elif matched_byte_count > 0:
120 # back up an try to start a new match after
121 # the first byte of the previous partial match
122 f.seek(f.tell() - matched_byte_count)
123 matched_byte_count = 0
127 if sys.hexversion >= 0x3030000:
128 orig_mtime = orig_stat.st_mtime_ns
129 os.utime(filename, ns=(orig_mtime, orig_mtime))
131 orig_mtime = orig_stat[stat.ST_MTIME]
132 os.utime(filename, (orig_mtime, orig_mtime))
135 def chpath_inplace_symlink(filename, st, old, new):
136 target = os.readlink(filename)
137 if target.startswith(old):
138 new_target = new + target[len(old):]
140 os.symlink(new_target, filename)
141 os.lchown(filename, st.st_uid, st.st_gid)
145 usage = "%s [options] <location> <old> <new>" % (os.path.basename(argv[0],))
146 parser = ArgumentParser(usage=usage)
147 options, args = parser.parse_known_args(argv[1:])
150 parser.error("3 args required, got %s" % (len(args),))
152 location, old, new = args
154 is_text_file = IsTextFile()
156 if not isinstance(location, bytes):
157 location = location.encode(FS_ENCODING)
158 if not isinstance(old, bytes):
159 old = old.encode(FS_ENCODING)
160 if not isinstance(new, bytes):
161 new = new.encode(FS_ENCODING)
163 st = os.lstat(location)
165 if stat.S_ISDIR(st.st_mode):
166 for parent, dirs, files in os.walk(location):
167 for filename in files:
168 filename = os.path.join(parent, filename)
170 st = os.lstat(filename)
174 if stat.S_ISREG(st.st_mode):
175 chpath_inplace(filename,
176 is_text_file(filename), old, new)
177 elif stat.S_ISLNK(st.st_mode):
178 chpath_inplace_symlink(filename, st, old, new)
180 elif stat.S_ISREG(st.st_mode):
181 chpath_inplace(location,
182 is_text_file(location), old, new)
183 elif stat.S_ISLNK(st.st_mode):
184 chpath_inplace_symlink(location, st, old, new)
188 if __name__ == "__main__":
189 sys.exit(main(sys.argv))