Coverage for browser.py: 60%

132 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1"""Module browser. 

2 

3XXX TO DO: 

4 

5- reparse when source changed (maybe just a button would be OK?) 

6 (or recheck on window popup) 

7- add popup menu with more options (e.g. doc strings, base classes, imports) 

8- add base classes to class browser tree 

9""" 

10 

11import os 

12import pyclbr 

13import sys 

14 

15from idlelib.config import idleConf 

16from idlelib import pyshell 

17from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas 

18from idlelib.util import py_extensions 

19from idlelib.window import ListedToplevel 

20 

21 

22file_open = None # Method...Item and Class...Item use this. 

23# Normally pyshell.flist.open, but there is no pyshell.flist for htest. 

24 

25# The browser depends on pyclbr and importlib which do not support .pyi files. 

26browseable_extension_blocklist = ('.pyi',) 

27 

28 

29def is_browseable_extension(path): 

30 _, ext = os.path.splitext(path) 1hde

31 ext = os.path.normcase(ext) 1hde

32 return ext in py_extensions and ext not in browseable_extension_blocklist 1hde

33 

34 

35def transform_children(child_dict, modname=None): 

36 """Transform a child dictionary to an ordered sequence of objects. 

37 

38 The dictionary maps names to pyclbr information objects. 

39 Filter out imported objects. 

40 Augment class names with bases. 

41 The insertion order of the dictionary is assumed to have been in line 

42 number order, so sorting is not necessary. 

43 

44 The current tree only calls this once per child_dict as it saves 

45 TreeItems once created. A future tree and tests might violate this, 

46 so a check prevents multiple in-place augmentations. 

47 """ 

48 obs = [] # Use list since values should already be sorted. 1acbfg

49 for key, obj in child_dict.items(): 1acbfg

50 if modname is None or obj.module == modname: 1acbfg

51 if hasattr(obj, 'super') and obj.super and obj.name == key: 1acbfg

52 # If obj.name != key, it has already been suffixed. 

53 supers = [] 

54 for sup in obj.super: 

55 if isinstance(sup, str): 55 ↛ 58line 55 didn't jump to line 58, because the condition on line 55 was never false

56 sname = sup 

57 else: 

58 sname = sup.name 

59 if sup.module != obj.module: 

60 sname = f'{sup.module}.{sname}' 

61 supers.append(sname) 

62 obj.name += '({})'.format(', '.join(supers)) 

63 obs.append(obj) 1acbfg

64 return obs 1acbfg

65 

66 

67class ModuleBrowser: 

68 """Browse module classes and functions in IDLE. 

69 """ 

70 # This class is also the base class for pathbrowser.PathBrowser. 

71 # Init and close are inherited, other methods are overridden. 

72 # PathBrowser.__init__ does not call __init__ below. 

73 

74 def __init__(self, master, path, *, _htest=False, _utest=False): 

75 """Create a window for browsing a module's structure. 

76 

77 Args: 

78 master: parent for widgets. 

79 path: full path of file to browse. 

80 _htest - bool; change box location when running htest. 

81 -utest - bool; suppress contents when running unittest. 

82 

83 Global variables: 

84 file_open: Function used for opening a file. 

85 

86 Instance variables: 

87 name: Module name. 

88 file: Full path and module with supported extension. 

89 Used in creating ModuleBrowserTreeItem as the rootnode for 

90 the tree and subsequently in the children. 

91 """ 

92 self.master = master 

93 self.path = path 

94 self._htest = _htest 

95 self._utest = _utest 

96 self.init() 

97 

98 def close(self, event=None): 

99 "Dismiss the window and the tree nodes." 

100 self.top.destroy() 

101 self.node.destroy() 

102 

103 def init(self): 

104 "Create browser tkinter widgets, including the tree." 

105 global file_open 

106 root = self.master 

107 flist = (pyshell.flist if not (self._htest or self._utest) 

108 else pyshell.PyShellFileList(root)) 

109 file_open = flist.open 

110 pyclbr._modules.clear() 

111 

112 # create top 

113 self.top = top = ListedToplevel(root) 

114 top.protocol("WM_DELETE_WINDOW", self.close) 

115 top.bind("<Escape>", self.close) 

116 if self._htest: # place dialog below parent if running htest 

117 top.geometry("+%d+%d" % 

118 (root.winfo_rootx(), root.winfo_rooty() + 200)) 

119 self.settitle() 

120 top.focus_set() 

121 

122 # create scrolled canvas 

123 theme = idleConf.CurrentTheme() 

124 background = idleConf.GetHighlight(theme, 'normal')['background'] 

125 sc = ScrolledCanvas(top, bg=background, highlightthickness=0, 

126 takefocus=1) 

127 sc.frame.pack(expand=1, fill="both") 

128 item = self.rootnode() 

129 self.node = node = TreeNode(sc.canvas, None, item) 

130 if not self._utest: 

131 node.update() 

132 node.expand() 

133 

134 def settitle(self): 

135 "Set the window title." 

136 self.top.wm_title("Module Browser - " + os.path.basename(self.path)) 

137 self.top.wm_iconname("Module Browser") 

138 

139 def rootnode(self): 

140 "Return a ModuleBrowserTreeItem as the root of the tree." 

141 return ModuleBrowserTreeItem(self.path) 

142 

143 

144class ModuleBrowserTreeItem(TreeItem): 

145 """Browser tree for Python module. 

146 

147 Uses TreeItem as the basis for the structure of the tree. 

148 Used by both browsers. 

149 """ 

150 

151 def __init__(self, file): 

152 """Create a TreeItem for the file. 

153 

154 Args: 

155 file: Full path and module name. 

156 """ 

157 self.file = file 1am

158 

159 def GetText(self): 

160 "Return the module name as the text string to display." 

161 return os.path.basename(self.file) 1n

162 

163 def GetIconName(self): 

164 "Return the name of the icon to display." 

165 return "python" 1o

166 

167 def GetSubList(self): 

168 "Return ChildBrowserTreeItems for children." 

169 return [ChildBrowserTreeItem(obj) for obj in self.listchildren()] 1i

170 

171 def OnDoubleClick(self): 

172 "Open a module in an editor window when double clicked." 

173 if not is_browseable_extension(self.file): 173 ↛ 174line 173 didn't jump to line 174, because the condition on line 173 was never true1e

174 return 

175 if not os.path.exists(self.file): 1e

176 return 1e

177 file_open(self.file) 1e

178 

179 def IsExpandable(self): 

180 "Return True if Python file." 

181 return is_browseable_extension(self.file) 1h

182 

183 def listchildren(self): 

184 "Return sequenced classes and functions in the module." 

185 if not is_browseable_extension(self.file): 185 ↛ 186line 185 didn't jump to line 186, because the condition on line 185 was never true1d

186 return [] 

187 dir, base = os.path.split(self.file) 1d

188 name, _ = os.path.splitext(base) 1d

189 try: 1d

190 tree = pyclbr.readmodule_ex(name, [dir] + sys.path) 1d

191 except ImportError: 

192 return [] 

193 return transform_children(tree, name) 1d

194 

195 

196class ChildBrowserTreeItem(TreeItem): 

197 """Browser tree for child nodes within the module. 

198 

199 Uses TreeItem as the basis for the structure of the tree. 

200 """ 

201 

202 def __init__(self, obj): 

203 "Create a TreeItem for a pyclbr class/function object." 

204 self.obj = obj 1acib

205 self.name = obj.name 1acib

206 self.isfunction = isinstance(obj, pyclbr.Function) 1acib

207 

208 def GetText(self): 

209 "Return the name of the function/class to display." 

210 name = self.name 1jb

211 if self.isfunction: 1jb

212 return "def " + name + "(...)" 1jb

213 else: 

214 return "class " + name 1jb

215 

216 def GetIconName(self): 

217 "Return the name of the icon to display." 

218 if self.isfunction: 1kb

219 return "python" 1kb

220 else: 

221 return "folder" 1kb

222 

223 def IsExpandable(self): 

224 "Return True if self.obj has nested objects." 

225 return self.obj.children != {} 1pb

226 

227 def GetSubList(self): 

228 "Return ChildBrowserTreeItems for children." 

229 return [ChildBrowserTreeItem(obj) 1cb

230 for obj in transform_children(self.obj.children)] 

231 

232 def OnDoubleClick(self): 

233 "Open module with file_open and position to lineno." 

234 try: 1l

235 edit = file_open(self.obj.file) 1l

236 edit.gotoline(self.obj.lineno) 1l

237 except (OSError, AttributeError): 

238 pass 

239 

240 

241def _module_browser(parent): # htest # 

242 if len(sys.argv) > 1: # If pass file on command line. 

243 file = sys.argv[1] 

244 else: 

245 file = __file__ 

246 # Add nested objects for htest. 

247 class Nested_in_func(TreeNode): 

248 def nested_in_class(): pass 

249 def closure(): 

250 class Nested_in_closure: pass 

251 ModuleBrowser(parent, file, _htest=True) 

252 

253if __name__ == "__main__": 253 ↛ 254line 253 didn't jump to line 254, because the condition on line 253 was never true

254 if len(sys.argv) == 1: # If pass file on command line, unittest fails. 

255 from unittest import main 

256 main('idlelib.idle_test.test_browser', verbosity=2, exit=False) 

257 from idlelib.idle_test.htest import run 

258 run(_module_browser)