|
31 | 31 | from __future__ import print_function
|
32 | 32 |
|
33 | 33 | __author__ = "Conan.io <info@conan.io>"
|
34 |
| -__version__ = "1.17" |
| 34 | +__version__ = "1.17.1" |
35 | 35 | __license__ = "MIT"
|
36 | 36 | __url__ = "https://github.com/conan-io/python-patch"
|
37 | 37 |
|
38 | 38 | import copy
|
39 | 39 | import logging
|
40 | 40 | import re
|
41 | 41 | import tempfile
|
| 42 | +import codecs |
42 | 43 |
|
43 | 44 | # cStringIO doesn't support unicode in 2.5
|
44 | 45 | try:
|
@@ -225,6 +226,73 @@ def pathstrip(path, n):
|
225 | 226 | # --- /Utility function ---
|
226 | 227 |
|
227 | 228 |
|
| 229 | +def decode_text(text): |
| 230 | + encodings = {codecs.BOM_UTF8: "utf_8_sig", |
| 231 | + codecs.BOM_UTF16_BE: "utf_16_be", |
| 232 | + codecs.BOM_UTF16_LE: "utf_16_le", |
| 233 | + codecs.BOM_UTF32_BE: "utf_32_be", |
| 234 | + codecs.BOM_UTF32_LE: "utf_32_le", |
| 235 | + b'\x2b\x2f\x76\x38': "utf_7", |
| 236 | + b'\x2b\x2f\x76\x39': "utf_7", |
| 237 | + b'\x2b\x2f\x76\x2b': "utf_7", |
| 238 | + b'\x2b\x2f\x76\x2f': "utf_7", |
| 239 | + b'\x2b\x2f\x76\x38\x2d': "utf_7"} |
| 240 | + for bom in sorted(encodings, key=len, reverse=True): |
| 241 | + if text.startswith(bom): |
| 242 | + try: |
| 243 | + return text[len(bom):].decode(encodings[bom]) |
| 244 | + except UnicodeDecodeError: |
| 245 | + continue |
| 246 | + decoders = ["utf-8", "Windows-1252"] |
| 247 | + for decoder in decoders: |
| 248 | + try: |
| 249 | + return text.decode(decoder) |
| 250 | + except UnicodeDecodeError: |
| 251 | + continue |
| 252 | + logger.warning("can't decode %s" % str(text)) |
| 253 | + return text.decode("utf-8", "ignore") # Ignore not compatible characters |
| 254 | + |
| 255 | + |
| 256 | +def to_file_bytes(content): |
| 257 | + if PY3K: |
| 258 | + if not isinstance(content, bytes): |
| 259 | + content = bytes(content, "utf-8") |
| 260 | + elif isinstance(content, unicode): |
| 261 | + content = content.encode("utf-8") |
| 262 | + return content |
| 263 | + |
| 264 | + |
| 265 | +def load(path, binary=False): |
| 266 | + """ Loads a file content """ |
| 267 | + with open(path, 'rb') as handle: |
| 268 | + tmp = handle.read() |
| 269 | + return tmp if binary else decode_text(tmp) |
| 270 | + |
| 271 | + |
| 272 | +def save(path, content, only_if_modified=False): |
| 273 | + """ |
| 274 | + Saves a file with given content |
| 275 | + Params: |
| 276 | + path: path to write file to |
| 277 | + content: contents to save in the file |
| 278 | + only_if_modified: file won't be modified if the content hasn't changed |
| 279 | + """ |
| 280 | + try: |
| 281 | + os.makedirs(os.path.dirname(path)) |
| 282 | + except Exception: |
| 283 | + pass |
| 284 | + |
| 285 | + new_content = to_file_bytes(content) |
| 286 | + |
| 287 | + if only_if_modified and os.path.exists(path): |
| 288 | + old_content = load(path, binary=True) |
| 289 | + if old_content == new_content: |
| 290 | + return |
| 291 | + |
| 292 | + with open(path, "wb") as handle: |
| 293 | + handle.write(new_content) |
| 294 | + |
| 295 | + |
228 | 296 | class Hunk(object):
|
229 | 297 | """ Parsed hunk data container (hunk starts with @@ -R +R @@) """
|
230 | 298 |
|
@@ -879,11 +947,46 @@ def _strip_prefix(self, filename):
|
879 | 947 | return filename[2:]
|
880 | 948 | return filename
|
881 | 949 |
|
| 950 | + def decode_clean(self, path, prefix): |
| 951 | + path = path.decode("utf-8").replace("\\", "/") |
| 952 | + if path.startswith(prefix): |
| 953 | + path = path[2:] |
| 954 | + return path |
| 955 | + |
| 956 | + def strip_path(self, path, base_path, strip=0): |
| 957 | + tokens = path.split("/") |
| 958 | + if len(tokens) > 1: |
| 959 | + tokens = tokens[strip:] |
| 960 | + path = "/".join(tokens) |
| 961 | + if base_path: |
| 962 | + path = os.path.join(base_path, path) |
| 963 | + return path |
| 964 | + # account for new and deleted files, upstream dep won't fix them |
| 965 | + |
| 966 | + |
| 967 | + |
| 968 | + |
882 | 969 | def apply(self, strip=0, root=None):
|
883 | 970 | """ Apply parsed patch, optionally stripping leading components
|
884 | 971 | from file paths. `root` parameter specifies working dir.
|
885 | 972 | return True on success
|
886 | 973 | """
|
| 974 | + items = [] |
| 975 | + for item in self.items: |
| 976 | + source = self.decode_clean(item.source, "a/") |
| 977 | + target = self.decode_clean(item.target, "b/") |
| 978 | + if "dev/null" in source: |
| 979 | + target = self.strip_path(target, root, strip) |
| 980 | + hunks = [s.decode("utf-8") for s in item.hunks[0].text] |
| 981 | + new_file = "".join(hunk[1:] for hunk in hunks) |
| 982 | + save(target, new_file) |
| 983 | + elif "dev/null" in target: |
| 984 | + source = self.strip_path(source, root, strip) |
| 985 | + os.unlink(source) |
| 986 | + else: |
| 987 | + items.append(item) |
| 988 | + self.items = items |
| 989 | + |
887 | 990 | if root:
|
888 | 991 | prevdir = os.getcwd()
|
889 | 992 | os.chdir(root)
|
|
0 commit comments