annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/grep.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
jpayne@68 1 """Grep dialog for Find in Files functionality.
jpayne@68 2
jpayne@68 3 Inherits from SearchDialogBase for GUI and uses searchengine
jpayne@68 4 to prepare search pattern.
jpayne@68 5 """
jpayne@68 6 import fnmatch
jpayne@68 7 import os
jpayne@68 8 import sys
jpayne@68 9
jpayne@68 10 from tkinter import StringVar, BooleanVar
jpayne@68 11 from tkinter.ttk import Checkbutton # Frame imported in ...Base
jpayne@68 12
jpayne@68 13 from idlelib.searchbase import SearchDialogBase
jpayne@68 14 from idlelib import searchengine
jpayne@68 15
jpayne@68 16 # Importing OutputWindow here fails due to import loop
jpayne@68 17 # EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow
jpayne@68 18
jpayne@68 19
jpayne@68 20 def grep(text, io=None, flist=None):
jpayne@68 21 """Open the Find in Files dialog.
jpayne@68 22
jpayne@68 23 Module-level function to access the singleton GrepDialog
jpayne@68 24 instance and open the dialog. If text is selected, it is
jpayne@68 25 used as the search phrase; otherwise, the previous entry
jpayne@68 26 is used.
jpayne@68 27
jpayne@68 28 Args:
jpayne@68 29 text: Text widget that contains the selected text for
jpayne@68 30 default search phrase.
jpayne@68 31 io: iomenu.IOBinding instance with default path to search.
jpayne@68 32 flist: filelist.FileList instance for OutputWindow parent.
jpayne@68 33 """
jpayne@68 34 root = text._root()
jpayne@68 35 engine = searchengine.get(root)
jpayne@68 36 if not hasattr(engine, "_grepdialog"):
jpayne@68 37 engine._grepdialog = GrepDialog(root, engine, flist)
jpayne@68 38 dialog = engine._grepdialog
jpayne@68 39 searchphrase = text.get("sel.first", "sel.last")
jpayne@68 40 dialog.open(text, searchphrase, io)
jpayne@68 41
jpayne@68 42
jpayne@68 43 def walk_error(msg):
jpayne@68 44 "Handle os.walk error."
jpayne@68 45 print(msg)
jpayne@68 46
jpayne@68 47
jpayne@68 48 def findfiles(folder, pattern, recursive):
jpayne@68 49 """Generate file names in dir that match pattern.
jpayne@68 50
jpayne@68 51 Args:
jpayne@68 52 folder: Root directory to search.
jpayne@68 53 pattern: File pattern to match.
jpayne@68 54 recursive: True to include subdirectories.
jpayne@68 55 """
jpayne@68 56 for dirpath, _, filenames in os.walk(folder, onerror=walk_error):
jpayne@68 57 yield from (os.path.join(dirpath, name)
jpayne@68 58 for name in filenames
jpayne@68 59 if fnmatch.fnmatch(name, pattern))
jpayne@68 60 if not recursive:
jpayne@68 61 break
jpayne@68 62
jpayne@68 63
jpayne@68 64 class GrepDialog(SearchDialogBase):
jpayne@68 65 "Dialog for searching multiple files."
jpayne@68 66
jpayne@68 67 title = "Find in Files Dialog"
jpayne@68 68 icon = "Grep"
jpayne@68 69 needwrapbutton = 0
jpayne@68 70
jpayne@68 71 def __init__(self, root, engine, flist):
jpayne@68 72 """Create search dialog for searching for a phrase in the file system.
jpayne@68 73
jpayne@68 74 Uses SearchDialogBase as the basis for the GUI and a
jpayne@68 75 searchengine instance to prepare the search.
jpayne@68 76
jpayne@68 77 Attributes:
jpayne@68 78 flist: filelist.Filelist instance for OutputWindow parent.
jpayne@68 79 globvar: String value of Entry widget for path to search.
jpayne@68 80 globent: Entry widget for globvar. Created in
jpayne@68 81 create_entries().
jpayne@68 82 recvar: Boolean value of Checkbutton widget for
jpayne@68 83 traversing through subdirectories.
jpayne@68 84 """
jpayne@68 85 super().__init__(root, engine)
jpayne@68 86 self.flist = flist
jpayne@68 87 self.globvar = StringVar(root)
jpayne@68 88 self.recvar = BooleanVar(root)
jpayne@68 89
jpayne@68 90 def open(self, text, searchphrase, io=None):
jpayne@68 91 """Make dialog visible on top of others and ready to use.
jpayne@68 92
jpayne@68 93 Extend the SearchDialogBase open() to set the initial value
jpayne@68 94 for globvar.
jpayne@68 95
jpayne@68 96 Args:
jpayne@68 97 text: Multicall object containing the text information.
jpayne@68 98 searchphrase: String phrase to search.
jpayne@68 99 io: iomenu.IOBinding instance containing file path.
jpayne@68 100 """
jpayne@68 101 SearchDialogBase.open(self, text, searchphrase)
jpayne@68 102 if io:
jpayne@68 103 path = io.filename or ""
jpayne@68 104 else:
jpayne@68 105 path = ""
jpayne@68 106 dir, base = os.path.split(path)
jpayne@68 107 head, tail = os.path.splitext(base)
jpayne@68 108 if not tail:
jpayne@68 109 tail = ".py"
jpayne@68 110 self.globvar.set(os.path.join(dir, "*" + tail))
jpayne@68 111
jpayne@68 112 def create_entries(self):
jpayne@68 113 "Create base entry widgets and add widget for search path."
jpayne@68 114 SearchDialogBase.create_entries(self)
jpayne@68 115 self.globent = self.make_entry("In files:", self.globvar)[0]
jpayne@68 116
jpayne@68 117 def create_other_buttons(self):
jpayne@68 118 "Add check button to recurse down subdirectories."
jpayne@68 119 btn = Checkbutton(
jpayne@68 120 self.make_frame()[0], variable=self.recvar,
jpayne@68 121 text="Recurse down subdirectories")
jpayne@68 122 btn.pack(side="top", fill="both")
jpayne@68 123
jpayne@68 124 def create_command_buttons(self):
jpayne@68 125 "Create base command buttons and add button for Search Files."
jpayne@68 126 SearchDialogBase.create_command_buttons(self)
jpayne@68 127 self.make_button("Search Files", self.default_command, isdef=True)
jpayne@68 128
jpayne@68 129 def default_command(self, event=None):
jpayne@68 130 """Grep for search pattern in file path. The default command is bound
jpayne@68 131 to <Return>.
jpayne@68 132
jpayne@68 133 If entry values are populated, set OutputWindow as stdout
jpayne@68 134 and perform search. The search dialog is closed automatically
jpayne@68 135 when the search begins.
jpayne@68 136 """
jpayne@68 137 prog = self.engine.getprog()
jpayne@68 138 if not prog:
jpayne@68 139 return
jpayne@68 140 path = self.globvar.get()
jpayne@68 141 if not path:
jpayne@68 142 self.top.bell()
jpayne@68 143 return
jpayne@68 144 from idlelib.outwin import OutputWindow # leave here!
jpayne@68 145 save = sys.stdout
jpayne@68 146 try:
jpayne@68 147 sys.stdout = OutputWindow(self.flist)
jpayne@68 148 self.grep_it(prog, path)
jpayne@68 149 finally:
jpayne@68 150 sys.stdout = save
jpayne@68 151
jpayne@68 152 def grep_it(self, prog, path):
jpayne@68 153 """Search for prog within the lines of the files in path.
jpayne@68 154
jpayne@68 155 For the each file in the path directory, open the file and
jpayne@68 156 search each line for the matching pattern. If the pattern is
jpayne@68 157 found, write the file and line information to stdout (which
jpayne@68 158 is an OutputWindow).
jpayne@68 159
jpayne@68 160 Args:
jpayne@68 161 prog: The compiled, cooked search pattern.
jpayne@68 162 path: String containing the search path.
jpayne@68 163 """
jpayne@68 164 folder, filepat = os.path.split(path)
jpayne@68 165 if not folder:
jpayne@68 166 folder = os.curdir
jpayne@68 167 filelist = sorted(findfiles(folder, filepat, self.recvar.get()))
jpayne@68 168 self.close()
jpayne@68 169 pat = self.engine.getpat()
jpayne@68 170 print(f"Searching {pat!r} in {path} ...")
jpayne@68 171 hits = 0
jpayne@68 172 try:
jpayne@68 173 for fn in filelist:
jpayne@68 174 try:
jpayne@68 175 with open(fn, errors='replace') as f:
jpayne@68 176 for lineno, line in enumerate(f, 1):
jpayne@68 177 if line[-1:] == '\n':
jpayne@68 178 line = line[:-1]
jpayne@68 179 if prog.search(line):
jpayne@68 180 sys.stdout.write(f"{fn}: {lineno}: {line}\n")
jpayne@68 181 hits += 1
jpayne@68 182 except OSError as msg:
jpayne@68 183 print(msg)
jpayne@68 184 print(f"Hits found: {hits}\n(Hint: right-click to open locations.)"
jpayne@68 185 if hits else "No hits.")
jpayne@68 186 except AttributeError:
jpayne@68 187 # Tk window has been closed, OutputWindow.text = None,
jpayne@68 188 # so in OW.write, OW.text.insert fails.
jpayne@68 189 pass
jpayne@68 190
jpayne@68 191
jpayne@68 192 def _grep_dialog(parent): # htest #
jpayne@68 193 from tkinter import Toplevel, Text, SEL, END
jpayne@68 194 from tkinter.ttk import Frame, Button
jpayne@68 195 from idlelib.pyshell import PyShellFileList
jpayne@68 196
jpayne@68 197 top = Toplevel(parent)
jpayne@68 198 top.title("Test GrepDialog")
jpayne@68 199 x, y = map(int, parent.geometry().split('+')[1:])
jpayne@68 200 top.geometry(f"+{x}+{y + 175}")
jpayne@68 201
jpayne@68 202 flist = PyShellFileList(top)
jpayne@68 203 frame = Frame(top)
jpayne@68 204 frame.pack()
jpayne@68 205 text = Text(frame, height=5)
jpayne@68 206 text.pack()
jpayne@68 207
jpayne@68 208 def show_grep_dialog():
jpayne@68 209 text.tag_add(SEL, "1.0", END)
jpayne@68 210 grep(text, flist=flist)
jpayne@68 211 text.tag_remove(SEL, "1.0", END)
jpayne@68 212
jpayne@68 213 button = Button(frame, text="Show GrepDialog", command=show_grep_dialog)
jpayne@68 214 button.pack()
jpayne@68 215
jpayne@68 216 if __name__ == "__main__":
jpayne@68 217 from unittest import main
jpayne@68 218 main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
jpayne@68 219
jpayne@68 220 from idlelib.idle_test.htest import run
jpayne@68 221 run(_grep_dialog)