Help Learning AppleScript

~ is short for your home directory, something like /Users/yourname/

Tried that already, no luck. And, in the user home directory there is only a folder called “Scripts”, not “Script Libraries”, which is why I thought it may be the System Folder where I would find “Script Libraries”, but there also there are only “Scripts” and “ScriptingAdditions” folders.

Hi.

That’s OK. Simply create the folder there yourself.

1 Like

Thanks, that worked. For those of us new to all this (and being former Military, I follow instructions precisely) they could have included that information in the enclosed “read me” file.

In any event, thanks, it’s working and I now have lots of reading to do.

The script you provided functions perfectly! I had asked about converting the output to PDF. Well, if you do enough Googling you can find anything. The script below converts a text document to a PDF. Question is (and I’ve tried for hours), how can I add this script to the end of your script to have it all done with one script.

tell application "TextEdit"
	activate
	open targetFilepath
	tell (windows whose id is not (get id of front window) and visible is true)
		set miniaturized to true
	end tell
	set bounds of front window to {279, 111, 1180, 719}
	
	tell application "System Events"
		click menu item "Export as PDF…" of menu 1 of menu bar item "File" of menu bar 1 of application process "TextEdit"
	end tell
end tell```

Homer712. That handler uses GUI scripting, which I know little about. Perhaps another forum member will be able to help you with that.

I found Shane’s handler that saves a text file as a PDF and got it to work with my script. I suspect I mangled Shane’s handler terribly, and there may be an alternative approach that is simpler. To test the script, open it in a script editor and run.

use framework "AppKit"
use framework "Foundation"
use scripting additions

set theFolder to (choose folder)
set foldersAndFiles to getFoldersAndFiles(theFolder)
writeFile(foldersAndFiles)

on getFoldersAndFiles(theFolder)
	set foldersAndFiles to {theFolder as text}
	tell application "Finder"
		set theFiles to (name of every file in theFolder)
		if theFiles ≠ {} then set the end of foldersAndFiles to theFiles
		try
			set theFolders to (every folder of the entire contents of theFolder) as alias list
		on error
			set theFolders to {}
		end try
		repeat with aFolder in theFolders
			set contents of aFolder to aFolder as text
		end repeat
		set theFolders to sortFolders(theFolders) of me
		repeat with aFolder in theFolders
			set end of foldersAndFiles to linefeed & aFolder
			set theFiles to name of every file in folder aFolder
			if theFiles ≠ {} then set the end of foldersAndFiles to theFiles
		end repeat
	end tell
	set {ATID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
	set foldersAndFiles to foldersAndFiles as text
	set AppleScript's text item delimiters to ATID
	return foldersAndFiles
end getFoldersAndFiles

on writeFile(theString) -- a rewrite of a handler by Shane Stanley
	set theFile to (POSIX path of (path to desktop)) & "Folder Contents.pdf"
	set theText to current application's NSAttributedString's alloc()'s initWithString:theString
	set printInf to current application's NSPrintInfo's sharedPrintInfo()'s |copy|()
	printInf's setJobDisposition:(current application's NSPrintSaveJob)
	printInf's dictionary()'s setObject:theFile forKey:(current application's NSPrintSavePath)
	set theSize to printInf's paperSize()
	set theLeft to printInf's leftMargin()
	set theRight to printInf's rightMargin()
	set theTop to printInf's topMargin()
	set theBottom to printInf's bottomMargin()
	set theView to current application's NSTextView's alloc()'s initWithFrame:{{0, 0}, {(width of theSize) - theLeft - theRight, (height of theSize) - theTop - theBottom}}
	theView's textStorage()'s setAttributedString:theText
	set theOp to current application's NSPrintOperation's printOperationWithView:theView printInfo:printInf
	theOp's setShowsPrintPanel:false
	theOp's setShowsProgressPanel:false
	theOp's runOperation()
end writeFile

on sortFolders(a)
	repeat with i from (count a) to 2 by -1
		repeat with j from 1 to i - 1
			if item j of a > item (j + 1) of a then
				set {item j of a, item (j + 1) of a} to {item (j + 1) of a, item j of a}
			end if
		end repeat
	end repeat
	return a
end sortFolders

you can also use this shell script:

cupsfilter test.txt > test.pdf

That’s an idea.

set theFolder to (choose folder)
set foldersAndFiles to getFoldersAndFiles(theFolder)
writeFile(foldersAndFiles)

on getFoldersAndFiles(theFolder)
	set foldersAndFiles to {theFolder as text}
	tell application "Finder"
		set theFiles to (name of every file in theFolder)
		if theFiles ≠ {} then set the end of foldersAndFiles to theFiles
		try
			set theFolders to (every folder of the entire contents of theFolder) as alias list
		on error
			set theFolders to {}
		end try
		repeat with aFolder in theFolders
			set contents of aFolder to aFolder as text
		end repeat
		set theFolders to sortFolders(theFolders) of me
		repeat with aFolder in theFolders
			set end of foldersAndFiles to linefeed & aFolder
			set theFiles to name of every file in folder aFolder
			if theFiles ≠ {} then set the end of foldersAndFiles to theFiles
		end repeat
	end tell
	set {ATID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
	set foldersAndFiles to foldersAndFiles as text
	set AppleScript's text item delimiters to ATID
	return foldersAndFiles
end getFoldersAndFiles

on writeFile(theText)
	set pdfMargins to 25
	set targetTextFile to (path to temporary items as text) & "Folder Contents.txt"
	set targetPdfFile to (path to desktop as text) & "Folder Contents.pdf"
	try
		set openedFile to (open for access file targetTextFile with write permission)
		set eof of openedFile to 0
		write theText to openedFile starting at eof
		close access openedFile
	on error
		try
			close access file targetTextFile
		end try
	end try
	do shell script "cupsfilter -D -o page-left=" & pdfMargins & " -o page-right=" & pdfMargins & " -o page-top=" & pdfMargins & " -o page-bottom=" & pdfMargins & " " & quoted form of POSIX path of targetTextFile & " > " & quoted form of POSIX path of targetPdfFile
end writeFile

on sortFolders(a)
	repeat with i from (count a) to 2 by -1
		repeat with j from 1 to i - 1
			if item j of a > item (j + 1) of a then
				set {item j of a, item (j + 1) of a} to {item (j + 1) of a, item j of a}
			end if
		end repeat
	end repeat
	return a
end sortFolders
1 Like

Thank you for the tip on this shell script.

This script works incredibly well, thank you for helping me along. The PDF appears on the desktop just as soon as I hit the run button.

@Homer712 Just a few helpful tips for you. I try to avoid GUI scripting whenever I can, however, sometimes it’s just unavoidable. One thing to consider while using GUI scripting is that some computers are faster than others and even sometimes your computer is slower today than it was yesterday. Considering that… adding delay commands to allow certain targeted UI Elements to become accessible for the code in your scripts to be able to perform actions on them, is sometimes necessary. Without the delay commands, sometimes your scripts will error out.

Compare this following code with the sample you posted. You can see how I added repeat loops to allow for certain UI elements to become available before your code performs actions on them. I prefer this method rather than just inserting a delay and some guessed number which I think would work. (ex: delay 3).

tell application "TextEdit"
	activate
	open targetFilepath
	tell (windows whose id is not (get id of front window) and visible is true)
		set miniaturized to true
	end tell
	set bounds of front window to {279, 111, 1180, 719}
end tell

tell application "System Events"
	set frontmost of process "TextEdit" to true
	click menu item "Export as PDF…" of menu 1 of ¬
		menu bar item "File" of menu bar 1 of process "TextEdit"
	repeat until button "Save" of sheet 1 of window 1 ¬
		of process "TextEdit" exists
		delay 0.1
	end repeat
	click button "Save" of sheet 1 of window 1 of process "TextEdit"
	repeat 10 times
		if button "Replace" of sheet 1 of sheet 1 of window 1 ¬
			of process "TextEdit" exists then
			click button "Replace" of sheet 1 of sheet 1 of ¬
				window 1 of process "TextEdit"
			exit repeat
		end if
		delay 0.1
	end repeat
end tell
1 Like

@robertfern, nice script, particularly for using System Events over Finder. It’s possible Ventura may behave differently, but on versions of macOS prior, the following isn’t required:

The statement being tried won’t ever throw an error provided theFolder is a valid reference to a container, which, in your script, is the case. If there are no folders in it, then the result of evaluating every folder is {}, and therefore the result of evaluating <sth> of every folder will also be {} provided that <sth> is any valid property or child element that belongs to a System Events folder class object (so path, POSIX path, files, and aliases should all be equally viable).

Obviously, this wouldn’t remain the case if evaluation is performed on the collection after requesting every folder but before retrieving their path properties. But, again, that’s not happening here.

If it so happens that Ventura has changed things on this front, then you can still avoid try-ing by predicating the evaluation of path upon the existence of every folder:

set theFolders to {}
tell every folder in theFolder to if ¬
        (exists) then set theFolders ¬
        to the path

Thanks for the tips. Right now, my “scripting” abilities are limited to searching for bits and pieces of AppleScript on the web that seem like they may do or perform an action that I’m interested in performing. After that, I may try to piece together a few lines of script that I found at different sites to see if I can get a complete action/process put together. It’s been a learning experience. I’ve been lucky finding MacScripter in that here I’ve had folks take what I’m trying to do and actually provide scripts for me to try. I’ll play with them some, maybe add/change a few lines and see what happens. On a side note, I had no idea that what I found and posted here was “GUI Scripting.” But thanks for your input and the revised script. I’m sure I’ll learn from it as well.

My script in post 28 contains a bug that is encountered when the target folder contains one folder only. Also, since the script uses the shell to create the PDF file, I thought it would make sense to use the shell to create the temporary text file.

set theFolder to (choose folder)
set foldersAndFiles to getFoldersAndFiles(theFolder)
writeFile(foldersAndFiles)

on getFoldersAndFiles(theFolder)
	set foldersAndFiles to {theFolder as text}
	tell application "Finder"
		set theFiles to (name of every file in theFolder)
		if theFiles ≠ {} then set the end of foldersAndFiles to theFiles
		try
			set theFolders to (every folder of the entire contents of theFolder)
			if class of theFolders is folder then set theFolders to theFolders as list
		on error
			set theFolders to {}
		end try
		repeat with aFolder in theFolders
			set contents of aFolder to aFolder as text
		end repeat
		set theFolders to sortFolders(theFolders) of me
		repeat with aFolder in theFolders
			set end of foldersAndFiles to linefeed & aFolder
			set theFiles to name of every file in folder aFolder
			if theFiles ≠ {} then set the end of foldersAndFiles to theFiles
		end repeat
	end tell
	set {ATID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
	set foldersAndFiles to foldersAndFiles as text
	set AppleScript's text item delimiters to ATID
	return foldersAndFiles
end getFoldersAndFiles

on writeFile(theText)
	set pdfMargins to 25
	set targetTextFile to (POSIX path of (path to temporary items)) & "Folder Contents.txt"
	set targetPdfFile to (POSIX path of (path to desktop)) & "Folder Contents.pdf"
	do shell script "echo " & quoted form of theText & " > " & quoted form of targetTextFile
	do shell script "cupsfilter -D -o page-left=" & pdfMargins & " -o page-right=" & pdfMargins & " -o page-top=" & pdfMargins & " -o page-bottom=" & pdfMargins & " " & quoted form of targetTextFile & " > " & quoted form of targetPdfFile
end writeFile

on sortFolders(a)
	repeat with i from (count a) to 2 by -1
		repeat with j from 1 to i - 1
			if item j of a > item (j + 1) of a then
				set {item j of a, item (j + 1) of a} to {item (j + 1) of a, item j of a}
			end if
		end repeat
	end repeat
	return a
end sortFolders

It had slipped my mind, but I first became aware that cupsfilter could be used to create a PDF in the following thread by bmose. In post 3 of this thread, bmose details some formatting options that can be used with cupsfilter.

Thanks, and even though (in this particular instance) I’m selecting a very specific folder, that always has 13 sub-folders, I’ll save your revised script for that “just in case.” I read through the thread you linked to, but right now the information contained is well above my level of understanding. I’m beginning to accept that learning AppleScript will be a journey, not a destination.

1 Like

Hey I’m not sure if you know about any of these. I come from a DJ background and have been curious about these silly mashup I’ve been seen posted where they have for example Johnny Cash singing something completely different. So I went investigating and came across these videos. that weren’t really related to what I was looking for but more catered towards “Speech” recognition and then converting to text and then to execute commands.

1 Like

@technomorph I appreciate your effort to help me with those suggestions. However, combining the built-in macOS Dictation Commands, Voice Control, and AppleScript’s Speech Recognition Suite… The control I have over my computer is far superior to the capabilities offered in those videos you posted. In fact, just to satisfy my own curiosities, I have run several tests to see how long I can go without having to use my mouse or keyboard. The last of my tests I was able to control my computer for 2 full weeks with only using my voice. It’s really quite impressive seeing it in action.

3 Likes

Thank you, thank you!

I have been working (off and on, with numerous sidetracks) on a small app, for my own personal use, just because I can’t find anything else that quite does a very simple (you would think) job: assigning a text name to each Mission Control Desktop Workspace. (More about that later, if you want.)

This is my first Mac app. I’ve written programs and shell scripts, and even a SketchUp plugin, but never an app.

I got to a place where the error message from Xcode said that I needed to define a handler, and I have had no idea what it meant. Every thing I could find from Googling seemed to just assume that it was part of my vocabulary. This is the first time I’ve had a concise definition that connected to what I already know.

About Handlers: …Handlers are also known as functions, subroutines, or methods.

Have you written up any outlines, tutorials, or How To guides about how you did this? I’m sure that there are many, many people who would be interested.

Before the internet exploded, I published a couple of How-To magazines/journals. The most public was “Inside SCO UNIX” that I kept going for nearly five years. Based on that, I am sure there is audience who would love to follow in your footsteps or use your work as a trailhead for further exploration, some of which you might turn around and make use of yourself.

It’s quite a simple process actually. The built in macOS voice control offers a lot of built in voice activated commands which actually will help accomplish a lot of tasks. For example, with voice commands activated, I can select any AppleScript file I have created in Finder, then speak the phrase “Make This Speakable”. This will then give me options to assign a voice command to use to trigger the running of that script.

As an example, I created an AppleScript which I named “Jim’s Favorites.scpt”. The code in that script will open Google Chrome and then opens 6 different URL’s in 6 different tabs for the sites I listed in the code. Then following the process I previously mentioned, I assigned a voice command to that file so now all I have to do is say “Jim’s Favorites” (the voice command I assigned it), and the script gets triggered and launches those websites for me.

I literally have created thousands of AppleScript and Shell scripts, some simple 1 liners and some very complex containing hundreds of lines of code, all of which I can run by speaking any of the phrases I assigned to those files.

Here are some of the voice commands I’ve created, to get an idea of what I’m talking about.

  • “Update History” - While in Terminal.app, speaking that command will run history -w, in every tab in Terminal which is currently not busy.

  • “Merge Windows” - While in Finder.app, speaking that command will close any duplicate Finder windows then merge all windows into tabs.

  • “Update Brew” - While in Terminal.app, speaking that command will run the script Auto_Brew_Update.sh … Which contains the following code.

#! /bin/bash -x

set +x 
export PS4='$LINENO + '

set -x 
/usr/local/bin/brew update
set +x 
sleep 2 1>/dev/null
set -x 
/usr/local/bin/brew upgrade --greedy
set +x 
sleep 2 1>/dev/null
set -x 
/usr/local/bin/brew doctor
set +x 
sleep 2 1>/dev/null
set -x 
/usr/local/bin/brew cleanup

Running that script updates and upgrades any of my outdated Homebrew installations (in debug mode) so I can observe the progress as it’s happening.

  • “Movie Search” - a friend of mine allows me to connect to his computer through FTP and download movies from him. If there is a movie that I don’t know what it is, I highlight the name of the movie then say the command “Movie Search”. This opens imdb.com and brings me to the info page for that movie.

The possibilities are literally endless but I think you can now get an idea of what I’m talking about.