Coverage for Applications / PyCharm.app / Contents / plugins / python-ce / helpers / pycharm / _jb_utils.py: 59%
83 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-01 16:37 -0600
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-01 16:37 -0600
1# coding=utf-8
3__author__ = 'Ilya.Kazakevich'
4import fnmatch
5import os
6import re
7import sys
10class FileChangesTracker(object):
11 """
12 On the instantiation the class records the timestampts of files stored in the folder.
13 #get_changed_files() return the list of files that have a timestamp different from the one they had during the class instantiation
16 """
18 def __init__(self, folder, patterns="*"):
19 self.old_files = self._get_changes_from(folder, patterns)
20 self.folder = folder
21 self.patterns = patterns
23 def get_changed_files(self):
24 assert self.folder, "No changes recorded"
25 new_files = self._get_changes_from(self.folder, patterns=self.patterns)
26 return filter(lambda f: f not in self.old_files or self.old_files[f] != new_files[f], new_files.keys())
28 @staticmethod
29 def _get_changes_from(folder, patterns):
30 result = {}
31 for tmp_folder, sub_dirs, files in os.walk(folder):
32 sub_dirs[:] = [s for s in sub_dirs if not s.startswith(".")]
33 if any(fnmatch.fnmatch(os.path.basename(tmp_folder), p) for p in patterns):
34 for file in map(lambda f: os.path.join(tmp_folder, f), files):
35 try:
36 result.update({file: os.path.getmtime(file)})
37 except OSError: # on Windows long path may lead to it: PY-23386
38 message = "PyCharm can't check if the following file been updated: {0}\n".format(str(file))
39 sys.stderr.write(message)
40 return result
43def jb_escape_output(output):
44 """
45 Escapes text in manner that is supported on Java side with CommandLineConsoleApi.kt#jbFilter
46 Check jbFilter doc for more info
48 :param output: raw text
49 :return: escaped text
50 """
51 return "##[jetbrains{0}".format(output)
54class OptionDescription(object):
55 """
56 Wrapper for argparse/optparse option (see VersionAgnosticUtils#get_options)
57 """
59 def __init__(self, name, description, action=None):
60 self.name = name
61 self.description = description
62 self.action = action
65class VersionAgnosticUtils(object):
66 """
67 "six" emulator: this class fabrics appropriate tool to use regardless python version.
68 Use it to write code that works both on py2 and py3. # TODO: Use Six instead
69 """
71 @staticmethod
72 def is_py3k():
73 return sys.version_info >= (3, 0)
75 @staticmethod
76 def __new__(cls, *more):
77 """
78 Fabrics Py2 or Py3 instance based on py version
79 """
80 real_class = _Py3KUtils if VersionAgnosticUtils.is_py3k() else _Py2Utils
81 return super(cls, real_class).__new__(real_class, *more)
83 def to_unicode(self, obj):
84 """
86 :param obj: string to convert to unicode
87 :return: unicode string
88 """
90 raise NotImplementedError()
92 def get_options(self, *args):
93 """
94 Hides agrparse/optparse difference
96 :param args: OptionDescription
97 :return: options namespace
98 """
99 raise NotImplementedError()
102class _Py2Utils(VersionAgnosticUtils):
103 """
104 Util for Py2
105 """
107 def to_unicode(self, obj):
108 if isinstance(obj, unicode):
109 return obj
110 try:
111 return unicode(obj) # Obj may have its own __unicode__
112 except (UnicodeDecodeError, AttributeError):
113 return unicode(str(obj).decode("utf-8")) # or it may have __str__
115 def get_options(self, *args):
116 import optparse
118 parser = optparse.OptionParser()
119 for option in args:
120 assert isinstance(option, OptionDescription)
121 parser.add_option(option.name, help=option.description, action=option.action)
122 (options, _) = parser.parse_args()
123 return options
126class _Py3KUtils(VersionAgnosticUtils):
127 """
128 Util for Py3
129 """
131 def to_unicode(self, obj):
132 return str(obj)
134 def get_options(self, *args):
135 import argparse
137 parser = argparse.ArgumentParser()
138 for option in args:
139 assert isinstance(option, OptionDescription)
140 parser.add_argument(option.name, help=option.description, action=option.action)
141 return parser.parse_args()
144def _parse_parametrized(part):
145 """
147 Support nose generators / pytest parameters and other functions that provides names like foo(1,2)
148 Until https://github.com/JetBrains/teamcity-messages/issues/121, all such tests are provided
149 with parentheses.
151 Tests with docstring are reported in similar way but they have space before parenthesis and should be ignored
152 by this function
154 """
155 match = re.match("^([^\\s)(]+)(\\(.+\\))$", part)
156 if not match:
157 return [part]
158 else:
159 return [match.group(1), match.group(2)]
162def test_to_list(test_name):
163 """
164 Splits test name to parts to use it as list.
165 It most cases dot is used, but runner may provide custom function
166 """
167 parts = test_name.split(".")
168 result = []
169 for part in parts:
170 result += _parse_parametrized(part)
171 return result