Monday, September 28, 2020
  • Index
  •  » Code Exchange
  •  » Batch find/replace text and text search inside multiple scripts

#1 2019-09-25 07:29:58 pm

t.spoon
Member
From:: BFE, Massachusetts
Registered: 2013-01-13
Posts: 484

Batch find/replace text and text search inside multiple scripts

Probably this is already out there somewhere, but I couldn't google it up so I wrote it.

This recurses through all scripts in a folder and its subfolders and does find/replace on one or more text strings in all the scripts.

It works on any plain-text .applescript files, compiled .scpt files, and it will find the .scpt files inside applescript .app's and get them too. It's using osadecompile and osacompile to get the .scpt files.

It also searches all the scripts for any strings and logs all found instances - it generates a .csv in your downloads folder that shows the file's modification date, which search text was found, the full path to the script it was found in, the line number in the script the string was found on, and the complete line of script text containing the found string. Note that it's saving a CSV, but the code lines can contain commas. For this reason, the code lines are put in quotes so they import to spreadsheets in a single column, ignoring any commas in the code.

Comments/improvements welcome.

Applescript:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


set masterScriptFolder to choose folder with prompt "Choose a folder of scripts to process a text change on. All instances will be changed, including all subfolders."

-- globals used in main script and handlers
global originalStrings -- list of text strings you want to replace
global newStrings -- list of text strings you'll replace them with
global logFileData -- to log instances of text you want to search for in the scripts
global logTextInstances -- list of the text strings you want to search for the occurance of
global thisScriptName -- check to see if a script with the name of this script is in the folder you're parsing. If it is, it will be skipped. Don't want to change your "find" value to the "replace" value inside the script you're running...


-- *** SET YOUR SEARCH AND REPLACEMENT STRINGS HERE ***

-- these will be found
set originalStrings to {"/Applications/Adobe Photoshop CS6/Adobe Photoshop CS6.app", "/Applications/Adobe Illustrator CS6/Adobe Illustrator.app"}

-- and replaced with the string in the equivalent positon in this list
set newStrings to {"Adobe Photoshop CC 2019", "Adobe Illustrator"}


-- This script also searches the processed scripts for text. Any strings in this list will be searched for in the script, and a log file will be saved to your downloads folder as a CSV containing these lines, what script they were found in, the line number, and the date modified of the file. If you only want to search your scripts and not perform a find/replace, then leave the above originalStrings and newStrings lists empty. Note, search occurs AFTER find/replace.
set logTextInstances to {"CS6"}
set logFileHeaders to "Date Modified,Search Text,Path,Line Number,Line Text" & return -- column headers for CSV
set logFileData to logFileHeaders
set thisScriptName to name of me

if (count of originalStrings) ≠ (count of newStrings) then display dialog "You don't have a replacement string for each original string. These lists must be the same length."

-- Handles recursion through directory tree and passing off parsable files to main handler
script_text_replace(masterScriptFolder)

if logFileData ≠ logFileHeaders then -- if there's been data added to log
   -- generate/save log file
   tell application "Finder"
       try
           set logSaveFile to ((path to downloads folder) & "Log File.csv" as string) as alias
       on error
           set logSaveFile to (make new file at (path to downloads folder as alias) with properties {name:"Log File.csv", file type:"TEXT", creator type:"ttxt"}) as alias
       end try
   end tell
   try
       set logFileRef to open for access logSaveFile with write permission
   on error
       close access logSaveFile
       set logFileRef to open for access logSaveFile with write permission
   end try
   set eof of logFileRef to 0
   write logFileData to logFileRef
   close access logFileRef
   set dataLogged to true
else
   set dataLogged to false
end if

-- make and display a dialog for when the script is finished
set endDialogText to "Complete."
if dataLogged is true then
   set endDialogText to endDialogText & return & "Your search string(s) resulted in a logged data CSV file called \"Log File\" in your Downloads folder."
   set dialogButtons to {"Done", "Open Log"}
   set defaultButton to "Open Log"
else
   set dialogButtons to {"Done"}
   set defaultButton to "Done"
end if

set dialogData to display dialog endDialogText buttons dialogButtons default button defaultButton
set buttonChoice to the button returned of dialogData
if buttonChoice is "Open Log" then tell application "Finder" to open logSaveFile



on script_text_replace(anItem)
   tell application "Finder"
       set anItem to anItem as alias
       if the kind of anItem is in {"Folder", "Application"} then --applications are actually bundles, and the Applescript code lives inside
           set subItems to every item of anItem
           repeat with anItem in subItems
               my script_text_replace(anItem) -- recurse through all files in all subfolders
           end repeat
       else
           set fileExtension to the name extension of anItem
           --ignore files we can't parse. Currently works on .app (the actual code is stored an .rtf) and .applescript, not on .scpt's (yet).
           if fileExtension is in {"applescript", "scpt"} then
               my change_text_in_file(anItem, fileExtension)
           end if
       end if
   end tell
end script_text_replace

-- Main function, handles the actual text replacement, search, log generation, and saving changes.
on change_text_in_file(theFile, fileExtension)
   tell application "Finder" to set fileName to the name of theFile
   if fileName ≠ thisScriptName then
       tell application "Finder" to set fileSize to the size of theFile
       if fileSize ≠ 0 then --skip empty files
           if fileExtension is "scpt" then
               set scriptPOSIXpath to quoted form of POSIX path of theFile
               set fileText to do shell script ("osadecompile " & scriptPOSIXpath)
           else if fileExtension is "applescript" then
               try
                   set fileRef to open for access theFile with write permission
               on error
                   close access theFile
                   set fileRef to open for access theFile with write permission
               end try
               set fileText to read fileRef
           end if
           set replacementItemsCount to count of originalStrings
           set outputText to fileText
           set textChanged to false --only bother saving if we change something
           -- go through each string we want to search for
           repeat with i from 1 to replacementItemsCount
               set anOriginalString to item i of originalStrings
               set aNewString to item i of newStrings
               if fileText contains anOriginalString then
                   --do the replace
                   set outputText to my replace_chars(fileText, anOriginalString, aNewString)
                   set textChanged to true
               end if
           end repeat
           --search for those strings we want to find and log
           set textParagraphs to paragraphs of outputText
           tell application "Finder"
               set filePath to theFile as text
               set modDate to the modification date of theFile
               set {year:y, day:d} to modDate
               tell modDate to set m to its month as integer
               set modDateText to m & "/" & d & "/" & y
           end tell
           set lineNumber to 0
           repeat with aParagraph in textParagraphs
               set lineNumber to lineNumber + 1
               repeat with someLogText in logTextInstances
                   if aParagraph contains someLogText then
                       -- add line to CSV for found match
                       set logFileData to logFileData & modDateText & "," & someLogText & "," & filePath & "," & lineNumber & "," & "\"" & aParagraph & "\"" & return
                   end if
               end repeat
           end repeat
           if textChanged is true then
               if fileExtension is "scpt" then
                   set shellCompileCommand to "osacompile -o " & scriptPOSIXpath & " << \"HEREDOC_END_TEXT\"" & linefeed & outputText & linefeed & "HEREDOC_END_TEXT"
                   do shell script shellCompileCommand
               else if fileExtension is "applescript" then
                   set eof of fileRef to 0 -- writes over existing log files
                   write outputText to fileRef
                   close access fileRef
               end if
           end if
       end if
       -- don't understand why the previous line occasionally fails, but without this next line here, Script Debugger regularly reports assets left open.
       if fileExtension is "applescript" then
           try
               close access fileRef
           end try
       end if
   end if
end change_text_in_file

-- standard find and replace
on replace_chars(theText, searchString, replacementString)
   set AppleScript's text item delimiters to the searchString
   set the item_list to every text item of theText
   set AppleScript's text item delimiters to the replacementString
   set theText to the item_list as string
   set AppleScript's text item delimiters to ""
   return theText
end replace_chars


Hackintosh built February, 2012 |  Mac OS Sierra
GIGABYTE GA-Z68X-UD3H-B3 | Core i5 2500k | 16 GB DDR3 | GIGABYTE Geforce 1050 TI 4GB
250 GB Samsung 850 EVO | 4 TB RAID
Dell Ultrasharp U3011 | Dell Ultrasharp 2007FPb

Offline

 

#2 2019-09-26 03:59:14 am

Nigel Garvey
Moderator
From:: Warwickshire, England
Registered: 2002-11-20
Posts: 5284

Re: Batch find/replace text and text search inside multiple scripts

t.spoon wrote:

Probably this is already out there somewhere, but I couldn't google it up so I wrote it.


smile

Hi t.spoon.

There's a script of mine from a couple of years ago on this very site (<https://macscripter.net/viewtopic.php?id=46031>), but it only replaces one string per sweep and doesn't keep a log. It does however offer a choice between editing files in place or creating new, edited copies.

In a quick test (replacing "--" comments with "#"), yours missed .app and .scptd files and didn't create a .csv — although in the latter case, it was because I'd misunderstood what was being logged. A second test with logTextInstances properly set to something in the scripts did produce a .csv. The result looked very smart when opened in Numbers, except that the modification dates were all in US m/d/y format, which is unreadable in other parts of the world. You can get short dates in the user's preferred format by replacing these three lines …

Applescript:

set {year:y, day:d} to modDate
tell modDate to set m to its month as integer
set modDateText to m & "/" & d & "/" & y

… with this:

Applescript:

set modDateText to modDate's short date string

Otherwise your script seems to work very well.  smile


NG

Offline

 

#3 2020-02-04 08:14:57 pm

t.spoon
Member
From:: BFE, Massachusetts
Registered: 2013-01-13
Posts: 484

Re: Batch find/replace text and text search inside multiple scripts

I finally got back to looking at this.

In my testing, it is working on .app files without any changes... I don't know what could cause it to not work for you but work for me.

It was missing .scptd files because I forgot to include "scptd" in the extensions it was looking for. (I never use them). I fixed that so it works with .scptd files now too.

I changed the date to be internationally-friendly as suggested.

Applescript:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions


set masterScriptFolder to choose folder with prompt "Choose a folder of scripts to process a text change on. All instances will be changed, including all subfolders."

-- globals used in main script and handlers
global originalStrings -- list of text strings you want to replace
global newStrings -- list of text strings you'll replace them with
global logFileData -- to log instances of text you want to search for in the scripts
global logTextInstances -- list of the text strings you want to search for the occurance of
global thisScriptName -- check to see if a script with the name of this script is in the folder you're parsing. If it is, it will be skipped. Don't want to change your "find" value to the "replace" value inside the script you're running...


-- *** SET YOUR SEARCH AND REPLACEMENT STRINGS HERE ***

-- these will be found
set originalStrings to {"/Applications/Adobe Photoshop CS6/Adobe Photoshop CS6.app", "/Applications/Adobe Illustrator CS6/Adobe Illustrator.app"}

-- and replaced with the string in the equivalent positon in this list
set newStrings to {"Adobe Photoshop CC 2019", "Adobe Illustrator"}


-- This script also searches the processed scripts for text. Any strings in this list will be searched for in the script, and a log file will be saved to your downloads folder as a CSV containing these lines, what script they were found in, the line number, and the date modified of the file. If you only want to search your scripts and not perform a find/replace, then leave the above originalStrings and newStrings lists empty. Note, search occurs AFTER find/replace.
set logTextInstances to {"CS6"}
set logFileHeaders to "Date Modified,Search Text,Path,Line Number,Line Text" & return -- column headers for CSV
set logFileData to logFileHeaders
set thisScriptName to name of me

if (count of originalStrings) ≠ (count of newStrings) then display dialog "You don't have a replacement string for each original string. These lists must be the same length."

-- Handles recursion through directory tree and passing off parsable files to main handler
script_text_replace(masterScriptFolder)

if logFileData ≠ logFileHeaders then -- if there's been data added to log
   -- generate/save log file
   tell application "Finder"
       try
           set logSaveFile to ((path to downloads folder) & "Log File.csv" as string) as alias
       on error
           set logSaveFile to (make new file at (path to downloads folder as alias) with properties {name:"Log File.csv", file type:"TEXT", creator type:"ttxt"}) as alias
       end try
   end tell
   try
       set logFileRef to open for access logSaveFile with write permission
   on error
       close access logSaveFile
       set logFileRef to open for access logSaveFile with write permission
   end try
   set eof of logFileRef to 0
   write logFileData to logFileRef
   close access logFileRef
   set dataLogged to true
else
   set dataLogged to false
end if

-- make and display a dialog for when the script is finished
set endDialogText to "Complete."
if dataLogged is true then
   set endDialogText to endDialogText & return & "Your search string(s) resulted in a logged data CSV file called \"Log File\" in your Downloads folder."
   set dialogButtons to {"Done", "Open Log"}
   set defaultButton to "Open Log"
else
   set dialogButtons to {"Done"}
   set defaultButton to "Done"
end if

set dialogData to display dialog endDialogText buttons dialogButtons default button defaultButton
set buttonChoice to the button returned of dialogData
if buttonChoice is "Open Log" then tell application "Finder" to open logSaveFile



on script_text_replace(anItem)
   tell application "Finder"
       set anItem to anItem as alias
       if the kind of anItem is in {"Folder", "Application"} then --applications are actually bundles, and the Applescript code lives inside
           set subItems to every item of anItem
           repeat with anItem in subItems
               my script_text_replace(anItem) -- recurse through all files in all subfolders
           end repeat
       else
           set fileExtension to the name extension of anItem
           if fileExtension is in {"applescript", "scpt", "scptd"} then
               my change_text_in_file(anItem, fileExtension)
           end if
       end if
   end tell
end script_text_replace

-- Main function, handles the actual text replacement, search, log generation, and saving changes.
on change_text_in_file(theFile, fileExtension)
   tell application "Finder" to set fileName to the name of theFile
   if fileName ≠ thisScriptName then
       tell application "Finder" to set fileSize to the size of theFile
       if fileSize ≠ 0 then --skip empty files
           if (fileExtension is "scpt") or (fileExtension is "scptd") then
               set scriptPOSIXpath to quoted form of POSIX path of theFile
               set fileText to do shell script ("osadecompile " & scriptPOSIXpath)
           else if fileExtension is "applescript" then
               try
                   set fileRef to open for access theFile with write permission
               on error
                   close access theFile
                   set fileRef to open for access theFile with write permission
               end try
               set fileText to read fileRef
           end if
           set replacementItemsCount to count of originalStrings
           set outputText to fileText
           set textChanged to false --only bother saving if we change something
           -- go through each string we want to search for
           repeat with i from 1 to replacementItemsCount
               set anOriginalString to item i of originalStrings
               set aNewString to item i of newStrings
               if fileText contains anOriginalString then
                   --do the replace
                   set outputText to my replace_chars(fileText, anOriginalString, aNewString)
                   set textChanged to true
               end if
           end repeat
           --search for those strings we want to find and log
           set textParagraphs to paragraphs of outputText
           tell application "Finder"
               set filePath to theFile as text
               set modDate to the modification date of theFile
               set modDateText to modDate's short date string
           end tell
           set lineNumber to 0
           repeat with aParagraph in textParagraphs
               set lineNumber to lineNumber + 1
               repeat with someLogText in logTextInstances
                   if aParagraph contains someLogText then
                       -- add line to CSV for found match
                       set logFileData to logFileData & modDateText & "," & someLogText & "," & filePath & "," & lineNumber & "," & "\"" & aParagraph & "\"" & return
                   end if
               end repeat
           end repeat
           if textChanged is true then
               if fileExtension is "scpt" or fileExtension is "scptd" then
                   set shellCompileCommand to "osacompile -o " & scriptPOSIXpath & " << \"HEREDOC_END_TEXT\"" & linefeed & outputText & linefeed & "HEREDOC_END_TEXT"
                   do shell script shellCompileCommand
               else if fileExtension is "applescript" then
                   set eof of fileRef to 0 -- writes over existing log files
                   write outputText to fileRef
                   close access fileRef
               end if
           end if
       end if
       -- don't understand why the previous line occasionally fails, but without this next line here, Script Debugger regularly reports assets left open.
       if fileExtension is "applescript" then
           try
               close access fileRef
           end try
       end if
   end if
end change_text_in_file

-- standard find and replace
on replace_chars(theText, searchString, replacementString)
   set AppleScript's text item delimiters to the searchString
   set the item_list to every text item of theText
   set AppleScript's text item delimiters to the replacementString
   set theText to the item_list as string
   set AppleScript's text item delimiters to ""
   return theText
end replace_chars


Hackintosh built February, 2012 |  Mac OS Sierra
GIGABYTE GA-Z68X-UD3H-B3 | Core i5 2500k | 16 GB DDR3 | GIGABYTE Geforce 1050 TI 4GB
250 GB Samsung 850 EVO | 4 TB RAID
Dell Ultrasharp U3011 | Dell Ultrasharp 2007FPb

Offline

 
  • Index
  •  » Code Exchange
  •  » Batch find/replace text and text search inside multiple scripts

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)