Handler to Get ASObjC NSURL

EDIT: 2017-03-16 8:56 PM CT


@ShaneStanley,

I keep running across various scripts that start with AppleScript paths, and then use them in ASObjC statements.  However, they sometimes fail because of:

* AppleScript path uses a tilde ("~")
* AppleScript path contains a folder that is a symlink.

So, based on numerous statements you have posted, I have cobbled together this handler.  I think it could be useful to a lot of folks.
I'd like this to be bullet proof, and work for all POSIX paths.

Could you please review, and suggest any improvements and/or consolidations that could be made?
Would this also handle an alias to a folder, in place of the symlink?

Thanks.

###Get ASObjC NSURL
```applescript
use scripting additions
use framework "Foundation"

--- START WITH TILDE PATH THAT IS A SYMLINK ---
set myShortPath to "~/Library/Script Libraries"

--- GET FULL EXPANDED NSURL FOR USE IN OTHER ASObjC STATEMENTS ---
set myNSURL to getNSURL(myShortPath) of me

--- GET FULL EXPANDED POSIX PATH FOR USE IN OTHER AppleScript STATEMENTS ---
set myFullPath to POSIX path of (myNSURL as text)

on getNSURL(pPosixPath)
  
  local curApp, nsPath, myNSURL
  
  set curApp to current application
  
  set nsPath to curApp's NSString's stringWithString:pPosixPath
  set nsPath to nsPath's stringByExpandingTildeInPath()
  set myNSURL to curApp's |NSURL|'s fileURLWithPath:nsPath
  set myNSURL to curApp's |NSURL|'s URLByResolvingAliasFileAtURL:myNSURL options:0 |error|:(missing value)
  
  return myNSURL
  
end getNSURL
```

If it resolves symlinks there, the handler should be fine – I don’t use symlinks, so I can’t check at the moment. Certainly URLByResolvingAliasFileAtURL: resolves aliases. If you wanted to be cautions, you could probably change stringByExpandingTildeInPath to stringByResolvingSymlinksInPath – I think that expands tildes too.

But this line:

set myFullPath to POSIX path of (myNSURL as text)

is problematic. You can’t coerce an NSURL to text. When you try, if you’re running 10.11 or later it generally works by coercing the URL to a file, and then coercing that. So it fails under 10.10, and it’s the long way home. Worse, in cases where the URL has not been resolved, the results can be unexpected. If you want the path, ask for it:

set myFullPath to myNSURL's |path|() as text

You should also include some error checking, by testing that myNSURL isn’t missing value, and throwing the appropriate error if it is. So the second-last line of the handler would be replaced with something like:

set {myNSURL, theError} to current application's |NSURL|'s URLByResolvingAliasFileAtURL:myNSURL options:0 |error|:(reference)
if myNSURL is missing value then error (theError's |description|() as text)
1 Like

Thanks. That works great!

Done.

set {myNSURL, theError} to current application's |NSURL|'s URLByResolvingAliasFileAtURL:myNSURL options:0 |error|:(reference)

if myNSURL is missing value then error (theError's |description|() as text)

I tried that, but I did not see any benefit.
No matter what kind of crazy path I tested, I did NOT get any errors.
For example:
~/Library: \\BAD Script Libraries

Also this statelment is another roundtrip to getting a NSURL, which I don’t need now that I have what you suggested above:

--- EXPAND TILDE, SYMLINK, AND ALIAS (if any exist) ---
  set nsPath to nsPath's stringByResolvingSymlinksInPath()
  
--- GETS THE NSURL, WHETHER OR NOT THE FILE/FOLDER EXISTS ---
  set myNSURL to curApp's |NSURL|'s fileURLWithPath:nsPath

I could not think of a good error test, since this returns the NSURL regardless of whether or not it is a valid path and whether or not the file/folder exists.
So, the ASObjC statement that uses the result will encounter an error, if any that were to occur.

Please let me know if you have any suggestions for an error test.

###Revised Handler Script

use scripting additions
use framework "Foundation"

--- START WITH TILDE PATH THAT IS A SYMLINK ---
set myShortPath to "~/Library/Script Libraries"

### TRIED THIS, BUT DID NOT GET ANY ERRORS ###
--set myShortPath to "~/Library: \\BAD Script Libraries"
-->(NSURL) file:///Users/jimunderwood/Library:%20%5CBAD%20Script%20Libraries
-->/Users/jimunderwood/Library: \BAD Script Libraries

--- GET FULL EXPANDED NSURL FOR USE IN OTHER ASObjC STATEMENTS ---
set myNSURL to getNSURL(myShortPath) of me
-->(NSURL) file:///Users/Shared/Dropbox/Mac%20Only/Alias%20Folders/Script%20Libraries/

--- GET FULL EXPANDED POSIX PATH FOR USE IN OTHER AppleScript STATEMENTS ---
set myFullPath to myNSURL's |path|() as text
-->"/Users/Shared/Dropbox/Mac Only/Alias Folders/Script Libraries"

on getNSURL(pPosixPath)
  (*  VER: 2.0    2017-03-16
  ---------------------------------------------------------------------------------
    PURPOSE:  Get NSURL from POSIX Path, whether or not the item exists
                  • Expands Tilde, Symlink, and/or Alias, if any exist
                  • Returned NSURL may not be a valid path
    
    REVISION NOTES:
      • Reflects comments by @ShaneStanley on 2017-03-16
          http://forum.latenightsw.com/t/handler-to-get-asobjc-nsurl/491/2
          • Except:  Did not add error test, since it did not trap any errors
  ---------------------------------------------------------------------------------  
  *)
  local curApp, nsPath, myNSURL
  
  set curApp to current application
  
  set nsPath to curApp's NSString's stringWithString:pPosixPath
  
  --- EXPAND TILDE, SYMLINK, AND ALIAS (if any exist) ---
  set nsPath to nsPath's stringByResolvingSymlinksInPath()
  
  --- GETS THE NSURL, WHETHER OR NOT THE FILE/FOLDER EXISTS ---
  set myNSURL to curApp's |NSURL|'s fileURLWithPath:nsPath
  
  ### DON'T NEED THIS NOW ###
  --set myNSURL to curApp's |NSURL|'s URLByResolvingAliasFileAtURL:myNSURL options:0 |error|:(missing value)
  
  ### THIS FAILS ###
  --set {myNSURL, theError} to curApp's |NSURL|'s fileURLWithPath:nsPath options:0 |error|:reference)
  
  ### THESE WORK, BUT I CAN'T FIND ANY PATH THAT TRIGGERS AN ERROR ###
  --set {myNSURL, theError} to current application's |NSURL|'s URLByResolvingAliasFileAtURL:myNSURL options:0 |error|:(reference)
  --  if myNSURL is missing value then error (theError's |description|() as text)
  
  
  return myNSURL
  
end getNSURL

I’m not sure what conditions it will fail under, other than when using different options. Perhaps when there are permissions problems.

If you want to resolve aliases as well as symlinks, I’d leave it in. Although stringByResolvingSymlinksInPath might also resolve aliases, it’s not documented to, and I’d be inclined not to take the risk. Alias resolution is one of those things that varies by OS version.

You are correct, as usual. :wink:

Without the URLByResolvingAliasFileAtURL:myNSURL statement, aliases are NOT resolved. But when I add that back in, aliases are resolved.

Thanks again for all your help.

I have updated my handler

  • based on all of @ShaneStanley’s comments, and further testing.
  • Added parameter to require that path exists
  • Added error handling

Shane, many, many thanks for all your help with this (and a bunch of other stuff).
I could not have done it without you.

I plan to use this handler in any script that requires a NSURL, since this handler will expand/resolve a POSIX path that has any/all of these:

  • Tilde (~) path
  • Symlink
  • Alias

and can optionally ensure that the path exists.

###getNSURL( ) 2.1

use scripting additions
use framework "Foundation"

--- EXAMPLE WITH TILDE PATH THAT INCLUDES SYMLINK ---
set myShortPath to "~/Library/Script Libraries"

--- EXAMPLE USING ALIAS FOR FOLDER ---
--set myShortPath to "~/Library/Scripts"

### EXAMPLE OF INVALID PATH ###
--  (No errors unless I require that path exist)

--set myShortPath to "~/Library: \\BAD Script Libraries"
-->(NSURL) file:///Users/jimunderwood/Library:%20%5CBAD%20Script%20Libraries
-->/Users/jimunderwood/Library: \BAD Script Libraries

--- GET FULL EXPANDED NSURL FOR USE IN OTHER ASObjC STATEMENTS ---

set myNSURL to getNSURL(myShortPath, true) of me
-->(NSURL) file:///Users/Shared/Dropbox/Mac%20Only/Alias%20Folders/Script%20Libraries/

--- GET FULL EXPANDED POSIX PATH STRING FOR USE IN OTHER AppleScript STATEMENTS ---

set myFullPath to myNSURL's |path|() as text
-->"/Users/Shared/Dropbox/Mac Only/Alias Folders/Script Libraries"

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--    HANDLERS
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

on getNSURL(pPosixPath, pEnsurePathExistsBool) --  @Path  @NSURL @ASObjC
  (*  VER: 2.1    2017-03-16
  ---------------------------------------------------------------------------------
    PURPOSE:  Get NSURL from POSIX Path
                  • Expands Tilde, Symlink, and/or Alias, if any exist
                  • Returned NSURL may not be a valid path
                  • Throw Error if Path does NOT exist, AND pEnsurePathExistsBool is true
    
    REVISION NOTES:
      • Reflects comments by @ShaneStanley on 2017-03-16
          http://forum.latenightsw.com/t/handler-to-get-asobjc-nsurl/491/2
          
    AUTHOR:  JMichaelTX
                • Accepting responsibility for all errors
                • ALL code came directly or indirectly from @ShaneStanley
                  (but I changed most variable names)
                • Thanks, Shane, for all your help!
  ---------------------------------------------------------------------------------  
  *)
  local curApp, nsPath, myNSURL, doesItExistBool
  
  set curApp to current application
  
  set nsPath to curApp's NSString's stringWithString:pPosixPath
  
  --- EXPAND TILDE & SYMLINK (if any exist) ---
  set nsPath to nsPath's stringByResolvingSymlinksInPath()
  
  --- GETS THE NSURL, WHETHER OR NOT THE FILE/FOLDER EXISTS ---
  set myNSURL to curApp's |NSURL|'s fileURLWithPath:nsPath
  
  --- NEEDED TO RESOLVE ALIASES ---
  set {myNSURL, nsError} to curApp's |NSURL|'s URLByResolvingAliasFileAtURL:myNSURL options:0 |error|:(reference)
  
  --- THROW ERROR IF NSURL WAS NOT SUCCESSFUL ---  
  if myNSURL is missing value then
    error "NSURL Error for Path: " & pPosixPath ¬
      & return & (nsError's |description|() as text)
  end if
  
  --- THROW ERROR IF PATH DOES NOT EXIST ---
  --    (& pEnsurePathExistsBool is true)
  if (pEnsurePathExistsBool) then
    set doesItExistBool to (myNSURL's checkResourceIsReachableAndReturnError:(missing value)) as boolean
    if (doesItExistBool is false) then
      error "Path does NOT exist: " & return & pPosixPath
    end if
  end if
  
  return myNSURL
  
end getNSURL