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