AppleScript – a frustrating beginning

Academia and professional work has exposed me to a few different programming paradigms: functional, logical, procedural and object-orientated. I usually get along fine with a new paradigm, starting off with a few example programs and hacking away at them to get a decent grasp of the language. But, I’m not getting anywhere with AppleScript – it’s a frustrating beastie!

I think the root cause is the documentation. A quick read of wikipedia’s page on AppleScript shows you you’re dealing with a natural language. AppleScript statements read like english sentences with padding words like “the” being optional, for example: tell application "Finder" to say the name of the front Finder window as string.

Maybe I’m being mislead because I’m holding onto Tiger while waiting for Snow Leopard – I can’t see why you’d need to use as string at the end of the example given? A Finder window’s name property is documented as unicode text and yet I need to explicitly convert it to a string (or equivalently and for no good reason as text works too). It’s rubbish!

Accesing properties is a little odd too; I’m not even sure whether AppleScripts trying to be object-oreiented or not. We can rewrite the example to use a posessive 's instead of the of operator. e.g. tell app "Finder" to say front Finder window's name as text – which is nice. But sometimes it works, and sometimes it doesn’t. (note: I arbitrarily abbreviated application to app, but I’ve no idea where to find a full list of supported abbreviations…!)

I don’t grok the Script Editor at all. You can easily inspect the command, classes and properties supported by an application, but where the hell’s the core language documentation? Where do you go to find out – when it comes to strings – count is a method, but length is a property? count "some string" is OK, but count of "some string" isn’t! How do I find a list of all properties of the AppleScript class – or any other arbitrary class, record or structure?

In the days and weeks ahead I hope to progress to such advanced topics as:

  • writing arbitrary text to standard output (this doesn’t work and I’d like to write to standard output whenever I need to, not solely at the end of the program.)
  • checking a file exists (without using Finder or throwing an error)
  • list files in a directory (without using do shell script or using Finder)
  • get the urls of all tabs of all windows of safari (tell app "Safari" to get URL of every document only shows one URL per window, and my natural language instinct to use every document of every tab of every window falls flat on its face…)
  • access the /dev directory (POSIX file "/usr" as alias is fine, POSIX file "/dev" as alias throws an error! WTF!?)

To end on a semi-useful note, here are some very basic things I’ve discovered while developing AppleScript’s in a terminal window:

Determine AppleScript version number
$ osascript -e "AppleScript's version"
Show current track name from iTunes
(Note: I’m coming round to the idea that using $'...' is the best way of wrapping the applescript on a bash command-line – it allows you to backslash escape single-quotes and keep your double-quotes handy for literal strings.)
$ osascript -e $'tell application "iTunes" to get current track's name'

(you’d think there’d be more…)

I’ll follow up with another AppleScript article once I’ve mastered the art of controlling iTunes to extract artwork and iterate over albums and artists.

Posted Thursday, April 2nd, 2009 under AppleScript.

2 comments

  1. • checking a file exists (without using Finder or throwing an error)

    AppleScript code:

    set itemexists to my itempathexists(path to desktop as text)
    -- set itemexists to my itempathexists("Foo:")
    on itempathexists(itempath)
    	try
    		set itemalias to itempath as alias
    		return true
    	on error
    		return false
    	end try
    end itempathexists

    • list files in a directory (without using do shell script or using Finder)

    AppleScript code:

    set folderfiles to list folder (path to desktop) with invisibles
    -- set folderfiles to list folder (path to desktop) without invisibles

    • get the urls of all tabs of all windows of safari

    AppleScript code:

    set URLlist to {}
    tell application "Safari"
    	set browserwindows to every window
    	repeat with browserwindow in browserwindows
    		set browserwindowURLs to URL of tabs of browserwindow
    		repeat with browserwindowURL in browserwindowURLs
    			set URLlist to URLlist & browserwindowURL
    		end repeat
    	end repeat
    end tell
    return URLlist

    • but where the hell’s the core language documentation?

    AppleScript Language Guide:

    http://developer.apple.com/documentation/applescript/conceptual/applescriptlangguide/index.html

    Hope it helps :-)

    Best regards from Berlin!

  2. Thanks Martin.

    I think a few of my frustrations were self-inflicted: the language guide is pretty good if you work through it properly. Trying to dive straight in via the index just didn’t work at all. After going back and (skim-)reading the lexical conventions (handy for list syntax) and chapter on applescript fundamentals really helped – and lead me to the rather handy class reference for the core AppleScript classes – though I’ve yet to discover if the Script Utility also has core class documentation readily available.

    It also turns out my early attempts to get all URLs out of Safari were stymied by some incompatibility having both WebKit and the Safari 4 Beta installed. In fact I get errors if I try chucking the code at WebKit now (I’ve only two browser windows open, but every window iterates over three. Not sure what’s going on there…)

    Even after skimming the language reference, I still can’t figure out why it’s OK to inline one of the set/repeat intermediaries, but not the other. I end up with this – which works fine, but it’d be nice to kill the try/end try and the tabURLs variable:

    
    set URLlist to {}
    tell app "WebKit"
      -- repeat with 'windows' works fine:
      repeat with browserwindow in windows
        try
          -- accessing tabs chucks an error for spurious windows
          tabs of browserwindow
    
          -- this repeat fails:
          -- repeat with tabURL in (URL of tabs of browserwindow)
    
          -- so we MUST use a temp variable...?
          set tabURLs to URL of tabs of browserwindow
          repeat with tabURL in tabURLs
            set URLlist to URLlist & tabURL
          end repeat
        end try
      end repeat
    end tell
    return URLlist