Coverage for grep.py: 44%
112 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
1"""Grep dialog for Find in Files functionality.
3 Inherits from SearchDialogBase for GUI and uses searchengine
4 to prepare search pattern.
5"""
6import fnmatch
7import os
8import sys
10from tkinter import StringVar, BooleanVar
11from tkinter.ttk import Checkbutton # Frame imported in ...Base
13from idlelib.searchbase import SearchDialogBase
14from idlelib import searchengine
16# Importing OutputWindow here fails due to import loop
17# EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow
20def grep(text, io=None, flist=None):
21 """Open the Find in Files dialog.
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.
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)
43def walk_error(msg):
44 "Handle os.walk error."
45 print(msg) 1g
48def findfiles(folder, pattern, recursive):
49 """Generate file names in dir that match pattern.
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
64class GrepDialog(SearchDialogBase):
65 "Dialog for searching multiple files."
67 title = "Find in Files Dialog"
68 icon = "Grep"
69 needwrapbutton = 0
71 def __init__(self, root, engine, flist):
72 """Create search dialog for searching for a phrase in the file system.
74 Uses SearchDialogBase as the basis for the GUI and a
75 searchengine instance to prepare the search.
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)
90 def open(self, text, searchphrase, io=None):
91 """Make dialog visible on top of others and ready to use.
93 Extend the SearchDialogBase open() to set the initial value
94 for globvar.
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))
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]
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")
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)
129 def default_command(self, event=None):
130 """Grep for search pattern in file path. The default command is bound
131 to <Return>.
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
152 def grep_it(self, prog, path):
153 """Search for prog within the lines of the files in path.
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).
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
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
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}")
202 flist = PyShellFileList(top)
203 frame = Frame(top)
204 frame.pack()
205 text = Text(frame, height=5)
206 text.pack()
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)
213 button = Button(frame, text="Show GrepDialog", command=show_grep_dialog)
214 button.pack()
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)
220 from idlelib.idle_test.htest import run
221 run(_grep_dialog)