Combined script application arguments

A script can receive arguments using run script or osascript, but a script application has more options. I’ve seen topics about various argument passing schemes, but nothing that combines several of them together.

Like a script, including a single parameter to the run handler allows it to receive arguments, but there are a few additional ways (other than the open hander) that a regular script application can be called, with slight variations in how arguments are passed:

  1. The application is double-clicked:
    The run handler is called with an empty list argument.
  2. Items are dropped onto the application:
    The open handler is called with a list of arguments.
  3. The application is reopened (double-clicking an active app, its Dock icon, etc):
    The reopen handler is called with a list of arguments.
  4. Using a handler called from a menu item or other UI control
    A regular script doesn’t use NSApplicationDelegate, so none of those methods (such as managing the Dock menu) can be used.
  5. Using the command line via osascript:
    This calls the run handler, passing a list of any arguments. Simple script applications can be used, but since osascript is primarily designed to run scripts, more complex scripts and AppleScriptObj-C can be problematic. Also remember that when a script is run, it will try to modify itself to update any properties and global variables (the historical way script values are persisted), so there may be a warning if this fails (read-only, code-signing, etc).
  6. Using the command line via open:
    The -a option specifies the application to use, and the --args option will pass following items as arguments. The run handler is called, but instead of an argument list, current application is passed. This can be tested for, and NSProcessInfo used to get the arguments - note is that the executable is passed in the first item of its argument list.
  7. Command-dragged into a Finder window’s toolbar or added to the Script Menu:
    These call the app like #1 or #3 above, with current application as the argument and only the executable in the NSProcessInfo argument list. This can also be used as an indication to get the current Finder selection.

Of course, using the Cocoa-AppleScript template gives you access to the various Cocoa delegate methods (including applicationDockMenu:), but for whatever reason you don’t see that used much these days (well, in forums anyway).

Although the following script can be run from the editor or menu, it is designed to be run as an application. The main differences when running from the editor or menu (other than the open and reopen handlers not being used) include the argument me being passed to the run handler, and different NSProcessInfo arguments. There may be other issues such as interactions with the Script Monitor app, but I haven’t tested much with that. For a script application though, a sample skeleton that can use the variations above would be something like:

use framework "Foundation" -- for AppleScriptObjC stuff
use scripting additions

property fileMenu : missing value -- outlet and flag for UI setup

to run args
   if fileMenu is missing value then setupUI()
   doStuff(getArguments(args))
end run

to open droppedFiles -- items dropped onto the app
   if fileMenu is missing value then setupUI()
   doStuff(droppedFiles)
end open

to reopen addedFiles -- stay-open app reopened (open app double-clicked, Dock menu, etc)
   if addedFiles is in {{}, current application} then -- no files, so look for other sources
      doStuff(getArguments(addedFiles))
   else
      doStuff(addedFiles)
   end if
end reopen

on setupUI() -- add an alternative to editing the Dock menu, as NSApplicationDelegate isn't used
   if name of current application is not in {"Script Debugger", "Script Editor"} then -- don't modify editor
      set my fileMenu to (current application's NSApp's mainMenu's itemWithTitle:"File")'s submenu
      fileMenu's removeItemAtIndex:0 -- don't need "Use Startup Screen" item
      (fileMenu's addItemWithTitle:"Open New Document" action:"menuAction:" keyEquivalent:"o")'s setTarget:me
   end if
end setupUI

on menuAction:sender -- action for added menu item (blank/new document, etc)
   doStuff({"Menu Item"}) -- {} or whatever
end menuAction:

on doStuff(theFiles) -- main handler to do stuff
   try
      set dialogTitle to "Selected file items"
      set {prevTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, return} -- for dialog
      if (class of theFiles is list) and ((count theFiles) > 0) then
         display dialog (theFiles as text) with title dialogTitle -- do stuff for selected file items
      else
         if theFiles is not missing value then
            display dialog "No selection" with title dialogTitle -- do stuff for no selection (blank/new file items, etc)
         else
            display dialog "Alternate for no selection" with title dialogTitle -- alternate for canceled choose dialog
         end if
      end if
   on error errmess
      display alert "Error Doing Stuff" message errmess
   end try
   set AppleScript's text item delimiters to prevTID
   return theFiles
end doStuff

to getArguments(args) -- get an argument list by going through the various sources
   try
      set selectedFiles to {} -- default
      if args is in {me, current application} then -- app double-clicked, 'open -a' with '--args', or script editor
         set processList to (current application's NSProcessInfo's processInfo's arguments) as list
         if (count processList) > 0 and first item of processList is not "/usr/bin/osascript" then -- skip if script menu
            set selectedFiles to rest of processList -- drop the first item (executable path)
         end if
      else if args is not in {} then -- osascript with arguments
         set selectedFiles to args
      end if
      set shiftKey to ((current application's NSEvent's modifierFlags()) div 131072 mod 2 is 1) -- NSEventModifierFlagShift
      if (not shiftKey) and (selectedFiles is {}) then -- shift key skips user selections
         tell application "Finder" to if (get windows) is not {} then -- try current Finder selection
            set selectedFiles to get selection as alias list -- note that this will be the app or alias if double-clicked
         end if
         if selectedFiles is {} or (((path to me) is in selectedFiles) and ((count selectedFiles) is 1)) then
            activate me
            set selectedFiles to (choose file with multiple selections allowed) -- of whatever type
         end if
      end if
      return selectedFiles
   on error errmess
      display alert "Error Getting Arguments" message errmess
      return missing value -- provide an alternate value for indication (cancel, etc)
   end try
end getArguments

From what I understand, the ‘reopen’ handler doesn’t have/require a parameter, so ‘addedFiles’ is not needed.
If files are drag & dropped on the already opened app, it will run the ‘open’ handler again, not ‘reopen’

Correct about the open handler. reopen shadows applicationShouldHandleReopen:hasVisibleWindows: and can take a parameter, although it appears to just be the sender (current application). I mainly pass it on to work with an app in the Finder toolbar, although reopen is also called when double-clicking or clicking the Dock icon for an already-opened app.