# coding=utf-8 __author__ = 'Ilya.Kazakevich' import fnmatch import os import sys class FileChangesTracker(object): """ On the instantiation the class records the timestampts of files stored in the folder. #get_changed_files() return the list of files that have a timestamp different from the one they had during the class instantiation """ def __init__(self, folder, patterns="*"): self.old_files = self._get_changes_from(folder, patterns) self.folder = folder self.patterns = patterns def get_changed_files(self): assert self.folder, "No changes recorded" new_files = self._get_changes_from(self.folder, patterns=self.patterns) return filter(lambda f: f not in self.old_files or self.old_files[f] != new_files[f], new_files.keys()) @staticmethod def _get_changes_from(folder, patterns): result = {} for tmp_folder, sub_dirs, files in os.walk(folder): sub_dirs[:] = [s for s in sub_dirs if not s.startswith(".")] if any(fnmatch.fnmatch(os.path.basename(tmp_folder), p) for p in patterns): for file in map(lambda f: os.path.join(tmp_folder, f), files): try: result.update({file: os.path.getmtime(file)}) except OSError: # on Windows long path may lead to it: PY-23386 message = "PyCharm can't check if the following file been updated: {0}\n".format(str(file)) sys.stderr.write(message) return result def jb_escape_output(output): """ Escapes text in manner that is supported on Java side with CommandLineConsoleApi.kt#jbFilter Check jbFilter doc for more info :param output: raw text :return: escaped text """ return "##[jetbrains{0}".format(output) class OptionDescription(object): """ Wrapper for argparse/optparse option (see VersionAgnosticUtils#get_options) """ def __init__(self, name, description, action=None): self.name = name self.description = description self.action = action class VersionAgnosticUtils(object): """ "six" emulator: this class fabrics appropriate tool to use regardless python version. Use it to write code that works both on py2 and py3. # TODO: Use Six instead """ @staticmethod def is_py3k(): return sys.version_info >= (3, 0) @staticmethod def __new__(cls, *more): """ Fabrics Py2 or Py3 instance based on py version """ real_class = _Py3KUtils if VersionAgnosticUtils.is_py3k() else _Py2Utils return super(cls, real_class).__new__(real_class, *more) def to_unicode(self, obj): """ :param obj: string to convert to unicode :return: unicode string """ raise NotImplementedError() def get_options(self, *args): """ Hides agrparse/optparse difference :param args: OptionDescription :return: options namespace """ raise NotImplementedError() class _Py2Utils(VersionAgnosticUtils): """ Util for Py2 """ def to_unicode(self, obj): if isinstance(obj, unicode): return obj try: return unicode(obj) # Obj may have its own __unicode__ except (UnicodeDecodeError, AttributeError): return unicode(str(obj).decode("utf-8")) # or it may have __str__ def get_options(self, *args): import optparse parser = optparse.OptionParser() for option in args: assert isinstance(option, OptionDescription) parser.add_option(option.name, help=option.description, action=option.action) (options, _) = parser.parse_args() return options class _Py3KUtils(VersionAgnosticUtils): """ Util for Py3 """ def to_unicode(self, obj): return str(obj) def get_options(self, *args): import argparse parser = argparse.ArgumentParser() for option in args: assert isinstance(option, OptionDescription) parser.add_argument(option.name, help=option.description, action=option.action) return parser.parse_args()