Coverage for Applications/PyCharm.app/Contents/plugins/python-ce/helpers/pycharm/_jb_utils.py: 59%

1# coding=utf-8

2

3 __author__ = 'Ilya.Kazakevich'

4 import fnmatch

5 import os

6 import re

7 import sys

8

9

10 class 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

43 def 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

54 class 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

65 class 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

102 class _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

126 class _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

144 def _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

162 def 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