Thursday, December 14, 2017

#1 2017-11-28 09:53:13 am

HoHollo
Member
Registered: 2015-07-10
Posts: 24

Batch convert text in .script files

Hi there,

how can I batch convert hundreds of Script-Editor .script documents in a folder and different subfolders to replace text "application1" with "application2" without opening the documents (Sierra)?

e.g. tell application "System Events" to tell process "application1"
    set frontmost to true

to

tell application "System Events" to tell process "application2"
    set frontmost to true

Thanks for your help.

Offline

 

#2 2017-11-30 05:39:06 am

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

Re: Batch convert text in .script files

Do you really have hundreds of script files requiring that change throughout?!  smile

Well. It's an interesting problem, although it requires the use of one of the system's private frameworks, for which there's no published documentation.

The script here displays a couple of dialogs which allow you to specify the search and replace strings to be universally applied, to choose whether to save the edited scripts to new files (recommended) or to overwrite the originals (at your own risk), and to choose the root folder of the hierarchy containing the files. It assumes that all the files have the correct name extensions for their type (not ".script") and saves the edited versions as the same type: compiled script file (".scpt"), compiled script bundle (".scptd"), script application (".app"), or uncompiled source code (".applescript"). It's not able to tell if script applications have been set to be stay-open or to display a start-up screen. Scripts not containing the search string aren't resaved. It's quite fast, but I haven't tried it with hundreds of files.

The two strings you mentioned are the default text in the first dialog, the quotes being literal characters in them.

Applescript:

main()

on main()
   script ASObjCStuff
       use AppleScript version "2.4" -- Yosemite (10.10) or later
       use framework "Foundation"
       use framework "OSAKit" -- No Xcode documentation.
       use scripting additions
       
       on doTheBusiness()
           global |⌘|, searchString, replaceString, savingToNewFiles, fileManager, directoryAndBundleKeys, directoryResult, skipHiddenFiles, relevantExtensions, storageTypes, extensionRegex, insertionTemplate
           
           -- Preset a few ASObjC values.
           set |⌘| to current application
           set fileManager to |⌘|'s class "NSFileManager"'s defaultManager()
           set directoryAndBundleKeys to |⌘|'s class "NSArray"'s arrayWithArray:({|⌘|'s NSURLIsDirectoryKey, |⌘|'s NSURLIsPackageKey})
           set directoryResult to |⌘|'s NSDictionary's dictionaryWithObjects:({true, false}) forKeys:(directoryAndBundleKeys)
           set skipHiddenFiles to |⌘|'s NSDirectoryEnumerationSkipsHiddenFiles
           set relevantExtensions to |⌘|'s class "NSArray"'s arrayWithArray:({"scpt", "scptd", "app", "applescript"})
           set storageTypes to |⌘|'s class "NSDictionary"'s dictionaryWithObjects:({|⌘|'s OSAStorageScriptType, |⌘|'s OSAStorageScriptBundleType, |⌘|'s OSAStorageApplicationBundleType, |⌘|'s OSAStorageTextType}) forKeys:(relevantExtensions)
           set extensionRegex to |⌘|'s class "NSString"'s stringWithString:("(?i)\\.(?:scptd?|app(?:lescript)?)$")
           set insertionTemplate to |⌘|'s class "NSString"'s stringWithString:(" (edited)$0")
           -- Get input from the user.
           set confirmation to missing value
           repeat until (confirmation is "OK")
               set {text returned:input, button returned:mode} to (display dialog "Please enter the search and replace strings on separate lines and click the appropriate button:" default answer ("\"application1\"" & linefeed & "\"application2\"") buttons {"Cancel", "Overwriting existing files", "Saving to new files"} cancel button 1 with title "Replace text in multiple script files")
               set {searchString, replaceString} to input's paragraphs
               set confirmation to button returned of (display dialog ("Replace every instance of:" & linefeed & "“" & searchString & "”" & linefeed & "with:" & linefeed & "“" & replaceString & "”?" & linefeed & mode & "?") buttons {"Cancel", "No. Go back", "OK"} cancel button 1 with title "Replace text in multiple script files")
           end repeat
           set mainFolderPath to POSIX path of (choose folder with prompt ("Please choose a folder hierarchy containing hundreds of script files in which" & linefeed & "every instance of “" & searchString & "” has to be changed to “" & replaceString & "”:"))
           -- If the script's still running, set ASObjC versions of the two strings and process the chosen folder.
           set searchString to |⌘|'s class "NSString"'s stringWithString:(searchString)
           set replaceString to |⌘|'s class "NSString"'s stringWithString:(replaceString)
           set savingToNewFiles to (mode is "Saving to new files")
           set mainFolderURL to |⌘|'s class "NSURL"'s fileURLWithPath:(mainFolderPath) isDirectory:(true)
           dealWithFolder(mainFolderURL)
       end doTheBusiness
       
       (* Recursively sift through the contents of a folder. *)
       on dealWithFolder(folderURL)
           global fileManager, directoryAndBundleKeys, directoryResult, skipHiddenFiles
           
           set thisFoldersContents to fileManager's contentsOfDirectoryAtURL:(folderURL) includingPropertiesForKeys:(directoryAndBundleKeys) options:(skipHiddenFiles) |error|:(missing value)
           repeat with thisURL in thisFoldersContents
               -- If this URL points to a folder, recurse to deal with its contents. Otherwise deal with the file or bundle.
               if (((thisURL's resourceValuesForKeys:(directoryAndBundleKeys) |error|:(missing value))'s isEqualToDictionary:(directoryResult)) as boolean) then
                   dealWithFolder(thisURL)
               else
                   dealWithFile(thisURL)
               end if
           end repeat
       end dealWithFolder
       
       (* Deal with a file or bundle, acting if it's a script. *)
       on dealWithFile(fileURL)
           global |⌘|, relevantExtensions, storageTypes, searchString, replaceString, savingToNewFiles, extensionRegex, insertionTemplate
           
           -- Act if the file has one of the recognised name extensions …
           set nameExtension to fileURL's pathExtension()'s lowercaseString()
           if ((relevantExtensions's containsObject:(nameExtension)) as boolean) then
               -- … and the script's successfully retrieved …
               set thisScript to (|⌘|'s class "OSAScript"'s alloc()'s initWithContentsOfURL:(fileURL) |error|:(missing value))
               if (thisScript is not missing value) then
                   -- … and its source code exists and contains the search string.
                   set sourceCode to thisScript's source()
                   if ((sourceCode is not missing value) and ((sourceCode's containsString:(searchString)) as boolean)) then
                       -- Edit the source code and make a new script document from it.
                       set editedCode to (sourceCode's stringByReplacingOccurrencesOfString:(searchString) withString:(replaceString))
                       set editedScript to (|⌘|'s class "OSAScript"'s alloc()'s initWithSource:(editedCode))
                       -- Sort out a file URL for saving the edited script.
                       if (savingToNewFiles) then
                           -- If saving to a new file, the new URL's path is the the old one with " (edited)" inserted before the extension dot.
                           set oldPath to fileURL's |path|()
                           set newPath to (oldPath's stringByReplacingOccurrencesOfString:(extensionRegex) withString:(insertionTemplate) options:(|⌘|'s NSRegularExpressionSearch) range:({0, oldPath's |length|()}))
                           set newURL to (|⌘|'s class "NSURL"'s fileURLWithPath:(newPath))
                       else
                           -- Otherwise the new URL _is_ the old.
                           set newURL to fileURL
                       end if
                       -- Determine the required script file type from the name extension.
                       set scriptFileType to storageTypes's objectForKey:(nameExtension)
                       -- Save the edited script.
                       tell editedScript to writeToURL:(newURL) ofType:(scriptFileType) usingStorageOptions:(|⌘|'s OSANull) |error|:(missing value)
                   end if
               end if
           end if
       end dealWithFile
   end script
   
   tell ASObjCStuff to doTheBusiness()
end main

Edit: Changed the ways of identifying relevant name extensions (from an array) and file-storage types (from a dictionary).

Last edited by Nigel Garvey (2017-11-30 09:14:49 am)


NG

Offline

 

#3 2017-11-30 06:55:59 am

HoHollo
Member
Registered: 2015-07-10
Posts: 24

Re: Batch convert text in .script files

Hello Nigel,

sorry, of course I meant .scpt. Your solution is awesome and works perfect. Thanks so much.
Yes, I have hundreds of .scpt files to control the usage of features of an symbol opentype font in applications that do not support Applescript such as Affinity Designer and Affinity Photo.

Thanks again!!

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)