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

1# coding=utf-8 

2 

3__author__ = 'Ilya.Kazakevich' 

4import fnmatch 

5import os 

6import re 

7import sys 

8 

9 

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 

14 

15 

16 """ 

17 

18 def __init__(self, folder, patterns="*"): 

19 self.old_files = self._get_changes_from(folder, patterns) 

20 self.folder = folder 

21 self.patterns = patterns 

22 

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()) 

27 

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 

41 

42 

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 

47 

48 :param output: raw text 

49 :return: escaped text 

50 """ 

51 return "##[jetbrains{0}".format(output) 

52 

53 

54class OptionDescription(object): 

55 """ 

56 Wrapper for argparse/optparse option (see VersionAgnosticUtils#get_options) 

57 """ 

58 

59 def __init__(self, name, description, action=None): 

60 self.name = name 

61 self.description = description 

62 self.action = action 

63 

64 

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 """ 

70 

71 @staticmethod 

72 def is_py3k(): 

73 return sys.version_info >= (3, 0) 

74 

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) 

82 

83 def to_unicode(self, obj): 

84 """ 

85 

86 :param obj: string to convert to unicode 

87 :return: unicode string 

88 """ 

89 

90 raise NotImplementedError() 

91 

92 def get_options(self, *args): 

93 """ 

94 Hides agrparse/optparse difference 

95  

96 :param args: OptionDescription 

97 :return: options namespace 

98 """ 

99 raise NotImplementedError() 

100 

101 

102class _Py2Utils(VersionAgnosticUtils): 

103 """ 

104 Util for Py2 

105 """ 

106 

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__ 

114 

115 def get_options(self, *args): 

116 import optparse 

117 

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 

124 

125 

126class _Py3KUtils(VersionAgnosticUtils): 

127 """ 

128 Util for Py3 

129 """ 

130 

131 def to_unicode(self, obj): 

132 return str(obj) 

133 

134 def get_options(self, *args): 

135 import argparse 

136 

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() 

142 

143 

144def _parse_parametrized(part): 

145 """ 

146 

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. 

150 

151 Tests with docstring are reported in similar way but they have space before parenthesis and should be ignored 

152 by this function 

153 

154 """ 

155 match = re.match("^([^\\s)(]+)(\\(.+\\))$", part) 

156 if not match: 

157 return [part] 

158 else: 

159 return [match.group(1), match.group(2)] 

160 

161 

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