annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/grep.py @ 69:33d812a61356

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