1# coding=utf-8 2 3__author__ = 'Ilya.Kazakevich' 4import fnmatch 5import os 6import sys 7 8 9class FileChangesTracker(object): 10 """ 11 On the instantiation the class records the timestampts of files stored in the folder. 12 #get_changed_files() return the list of files that have a timestamp different from the one they had during the class instantiation 13 14 15 """ 16 17 def __init__(self, folder, patterns="*"): 18 self.old_files = self._get_changes_from(folder, patterns) 19 self.folder = folder 20 self.patterns = patterns 21 22 def get_changed_files(self): 23 assert self.folder, "No changes recorded" 24 new_files = self._get_changes_from(self.folder, patterns=self.patterns) 25 return filter(lambda f: f not in self.old_files or self.old_files[f] != new_files[f], new_files.keys()) 26 27 @staticmethod 28 def _get_changes_from(folder, patterns): 29 result = {} 30 for tmp_folder, sub_dirs, files in os.walk(folder): 31 sub_dirs[:] = [s for s in sub_dirs if not s.startswith(".")] 32 if any(fnmatch.fnmatch(os.path.basename(tmp_folder), p) for p in patterns): 33 for file in map(lambda f: os.path.join(tmp_folder, f), files): 34 try: 35 result.update({file: os.path.getmtime(file)}) 36 except OSError: # on Windows long path may lead to it: PY-23386 37 message = "PyCharm can't check if the following file been updated: {0}\n".format(str(file)) 38 sys.stderr.write(message) 39 return result 40 41 42def jb_escape_output(output): 43 """ 44 Escapes text in manner that is supported on Java side with CommandLineConsoleApi.kt#jbFilter 45 Check jbFilter doc for more info 46 47 :param output: raw text 48 :return: escaped text 49 """ 50 return "##[jetbrains{0}".format(output) 51 52 53class OptionDescription(object): 54 """ 55 Wrapper for argparse/optparse option (see VersionAgnosticUtils#get_options) 56 """ 57 58 def __init__(self, name, description, action=None): 59 self.name = name 60 self.description = description 61 self.action = action 62 63 64class VersionAgnosticUtils(object): 65 """ 66 "six" emulator: this class fabrics appropriate tool to use regardless python version. 67 Use it to write code that works both on py2 and py3. # TODO: Use Six instead 68 """ 69 70 @staticmethod 71 def is_py3k(): 72 return sys.version_info >= (3, 0) 73 74 @staticmethod 75 def __new__(cls, *more): 76 """ 77 Fabrics Py2 or Py3 instance based on py version 78 """ 79 real_class = _Py3KUtils if VersionAgnosticUtils.is_py3k() else _Py2Utils 80 return super(cls, real_class).__new__(real_class, *more) 81 82 def to_unicode(self, obj): 83 """ 84 85 :param obj: string to convert to unicode 86 :return: unicode string 87 """ 88 89 raise NotImplementedError() 90 91 def get_options(self, *args): 92 """ 93 Hides agrparse/optparse difference 94 95 :param args: OptionDescription 96 :return: options namespace 97 """ 98 raise NotImplementedError() 99 100 101class _Py2Utils(VersionAgnosticUtils): 102 """ 103 Util for Py2 104 """ 105 106 def to_unicode(self, obj): 107 if isinstance(obj, unicode): 108 return obj 109 try: 110 return unicode(obj) # Obj may have its own __unicode__ 111 except (UnicodeDecodeError, AttributeError): 112 return unicode(str(obj).decode("utf-8")) # or it may have __str__ 113 114 def get_options(self, *args): 115 import optparse 116 117 parser = optparse.OptionParser() 118 for option in args: 119 assert isinstance(option, OptionDescription) 120 parser.add_option(option.name, help=option.description, action=option.action) 121 (options, _) = parser.parse_args() 122 return options 123 124 125class _Py3KUtils(VersionAgnosticUtils): 126 """ 127 Util for Py3 128 """ 129 130 def to_unicode(self, obj): 131 return str(obj) 132 133 def get_options(self, *args): 134 import argparse 135 136 parser = argparse.ArgumentParser() 137 for option in args: 138 assert isinstance(option, OptionDescription) 139 parser.add_argument(option.name, help=option.description, action=option.action) 140 return parser.parse_args() 141