Coverage for grep.py: 44%

112 statements  

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

1"""Grep dialog for Find in Files functionality. 

2 

3 Inherits from SearchDialogBase for GUI and uses searchengine 

4 to prepare search pattern. 

5""" 

6import fnmatch 

7import os 

8import sys 

9 

10from tkinter import StringVar, BooleanVar 

11from tkinter.ttk import Checkbutton # Frame imported in ...Base 

12 

13from idlelib.searchbase import SearchDialogBase 

14from idlelib import searchengine 

15 

16# Importing OutputWindow here fails due to import loop 

17# EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow 

18 

19 

20def grep(text, io=None, flist=None): 

21 """Open the Find in Files dialog. 

22 

23 Module-level function to access the singleton GrepDialog 

24 instance and open the dialog. If text is selected, it is 

25 used as the search phrase; otherwise, the previous entry 

26 is used. 

27 

28 Args: 

29 text: Text widget that contains the selected text for 

30 default search phrase. 

31 io: iomenu.IOBinding instance with default path to search. 

32 flist: filelist.FileList instance for OutputWindow parent. 

33 """ 

34 root = text._root() 

35 engine = searchengine.get(root) 

36 if not hasattr(engine, "_grepdialog"): 

37 engine._grepdialog = GrepDialog(root, engine, flist) 

38 dialog = engine._grepdialog 

39 searchphrase = text.get("sel.first", "sel.last") 

40 dialog.open(text, searchphrase, io) 

41 

42 

43def walk_error(msg): 

44 "Handle os.walk error." 

45 print(msg) 1g

46 

47 

48def findfiles(folder, pattern, recursive): 

49 """Generate file names in dir that match pattern. 

50 

51 Args: 

52 folder: Root directory to search. 

53 pattern: File pattern to match. 

54 recursive: True to include subdirectories. 

55 """ 

56 for dirpath, _, filenames in os.walk(folder, onerror=walk_error): 1degfbc

57 yield from (os.path.join(dirpath, name) 1defbc

58 for name in filenames 

59 if fnmatch.fnmatch(name, pattern)) 

60 if not recursive: 1defbc

61 break 1defbc

62 

63 

64class GrepDialog(SearchDialogBase): 

65 "Dialog for searching multiple files." 

66 

67 title = "Find in Files Dialog" 

68 icon = "Grep" 

69 needwrapbutton = 0 

70 

71 def __init__(self, root, engine, flist): 

72 """Create search dialog for searching for a phrase in the file system. 

73 

74 Uses SearchDialogBase as the basis for the GUI and a 

75 searchengine instance to prepare the search. 

76 

77 Attributes: 

78 flist: filelist.Filelist instance for OutputWindow parent. 

79 globvar: String value of Entry widget for path to search. 

80 globent: Entry widget for globvar. Created in 

81 create_entries(). 

82 recvar: Boolean value of Checkbutton widget for 

83 traversing through subdirectories. 

84 """ 

85 super().__init__(root, engine) 

86 self.flist = flist 

87 self.globvar = StringVar(root) 

88 self.recvar = BooleanVar(root) 

89 

90 def open(self, text, searchphrase, io=None): 

91 """Make dialog visible on top of others and ready to use. 

92 

93 Extend the SearchDialogBase open() to set the initial value 

94 for globvar. 

95 

96 Args: 

97 text: Multicall object containing the text information. 

98 searchphrase: String phrase to search. 

99 io: iomenu.IOBinding instance containing file path. 

100 """ 

101 SearchDialogBase.open(self, text, searchphrase) 

102 if io: 

103 path = io.filename or "" 

104 else: 

105 path = "" 

106 dir, base = os.path.split(path) 

107 head, tail = os.path.splitext(base) 

108 if not tail: 

109 tail = ".py" 

110 self.globvar.set(os.path.join(dir, "*" + tail)) 

111 

112 def create_entries(self): 

113 "Create base entry widgets and add widget for search path." 

114 SearchDialogBase.create_entries(self) 

115 self.globent = self.make_entry("In files:", self.globvar)[0] 

116 

117 def create_other_buttons(self): 

118 "Add check button to recurse down subdirectories." 

119 btn = Checkbutton( 

120 self.make_frame()[0], variable=self.recvar, 

121 text="Recurse down subdirectories") 

122 btn.pack(side="top", fill="both") 

123 

124 def create_command_buttons(self): 

125 "Create base command buttons and add button for Search Files." 

126 SearchDialogBase.create_command_buttons(self) 

127 self.make_button("Search Files", self.default_command, isdef=True) 

128 

129 def default_command(self, event=None): 

130 """Grep for search pattern in file path. The default command is bound 

131 to <Return>. 

132 

133 If entry values are populated, set OutputWindow as stdout 

134 and perform search. The search dialog is closed automatically 

135 when the search begins. 

136 """ 

137 prog = self.engine.getprog() 

138 if not prog: 

139 return 

140 path = self.globvar.get() 

141 if not path: 

142 self.top.bell() 

143 return 

144 from idlelib.outwin import OutputWindow # leave here! 

145 save = sys.stdout 

146 try: 

147 sys.stdout = OutputWindow(self.flist) 

148 self.grep_it(prog, path) 

149 finally: 

150 sys.stdout = save 

151 

152 def grep_it(self, prog, path): 

153 """Search for prog within the lines of the files in path. 

154 

155 For the each file in the path directory, open the file and 

156 search each line for the matching pattern. If the pattern is 

157 found, write the file and line information to stdout (which 

158 is an OutputWindow). 

159 

160 Args: 

161 prog: The compiled, cooked search pattern. 

162 path: String containing the search path. 

163 """ 

164 folder, filepat = os.path.split(path) 1bc

165 if not folder: 165 ↛ 166line 165 didn't jump to line 166, because the condition on line 165 was never true1bc

166 folder = os.curdir 

167 filelist = sorted(findfiles(folder, filepat, self.recvar.get())) 1bc

168 self.close() 1bc

169 pat = self.engine.getpat() 1bc

170 print(f"Searching {pat!r} in {path} ...") 1bc

171 hits = 0 1bc

172 try: 1bc

173 for fn in filelist: 1bc

174 try: 1bc

175 with open(fn, errors='replace') as f: 1bc

176 for lineno, line in enumerate(f, 1): 1bc

177 if line[-1:] == '\n': 177 ↛ 179line 177 didn't jump to line 179, because the condition on line 177 was never false1bc

178 line = line[:-1] 1bc

179 if prog.search(line): 1bc

180 sys.stdout.write(f"{fn}: {lineno}: {line}\n") 1b

181 hits += 1 1b

182 except OSError as msg: 

183 print(msg) 

184 print(f"Hits found: {hits}\n(Hint: right-click to open locations.)" 1bc

185 if hits else "No hits.") 

186 except AttributeError: 

187 # Tk window has been closed, OutputWindow.text = None, 

188 # so in OW.write, OW.text.insert fails. 

189 pass 

190 

191 

192def _grep_dialog(parent): # htest # 

193 from tkinter import Toplevel, Text, SEL, END 

194 from tkinter.ttk import Frame, Button 

195 from idlelib.pyshell import PyShellFileList 

196 

197 top = Toplevel(parent) 

198 top.title("Test GrepDialog") 

199 x, y = map(int, parent.geometry().split('+')[1:]) 

200 top.geometry(f"+{x}+{y + 175}") 

201 

202 flist = PyShellFileList(top) 

203 frame = Frame(top) 

204 frame.pack() 

205 text = Text(frame, height=5) 

206 text.pack() 

207 

208 def show_grep_dialog(): 

209 text.tag_add(SEL, "1.0", END) 

210 grep(text, flist=flist) 

211 text.tag_remove(SEL, "1.0", END) 

212 

213 button = Button(frame, text="Show GrepDialog", command=show_grep_dialog) 

214 button.pack() 

215 

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

217 from unittest import main 

218 main('idlelib.idle_test.test_grep', verbosity=2, exit=False) 

219 

220 from idlelib.idle_test.htest import run 

221 run(_grep_dialog)