Relative FolderItem Path Resolution

There have been a number of posts about the changes to FolderItems, both from an API1 to API2 perspective, and in regards to the recent change to macOS behavior. In some of my apps I store relative paths for resolution during certain processes, so I cleaned up and properly commented a function for resolving these. Providing this in the hopes that someone finds it useful. If you have better ways of implementing this or can see any errors/omissions, please feel free to post them.

I made some choices that I felt were “lesser of two evils” such as stripping any trailing slash then attempting to determine if the target is a file using the existence of a . in the last part. This fits my usage, but Your Mileage May Vary. An alternative would be check for a trailing slash and existence of a . in the last part, but I felt that would be less flexible and I plan to replace my own older code in some projects to use this method so I need that built-in flexibility based on old assumptions.

Note that I’ve found this sort of relative path resolution function especially helpful in conjunction with security scoped bookmarks on macOS.

The Function

Paste this function into a module, make sure its Scope is Public.

Public Function ChildRelative(extends relativeTo as FolderItem, path as String, createMissingFolders as Boolean = False) As FolderItem
  '// Exit early if the base path is nil, though theoretically this probablye
  '   will never be needed.
  if relativeTo is nil then Return nil
  
  '// Ensure that our root path exists if createMissingFolders = True
  if relativeTo.Exists = False and createMissingFolders = True then relativeTo.CreateFolder
  
  '// current is the variable we'll be using for our return loop.
  var current as FolderItem = relativeTo
  
  '// Break apart the path string.
  var parts() as String = path.ToArray( "/" )
  
  '// Sanitize the input string.
  if path.Left( 1 ) = "/" then parts.RemoveAt(0) '// Remove leading slash.
  if path.Right( 1 ) = "/" and parts.LastIndex >= 0 then parts.RemoveAt(parts.LastIndex) '// Remove trailing slash.
  if parts.LastIndex = 0 and parts(0).Trim.IsEmpty then Return current '// If the parts array now consists of an empty string, return the relative root.
  
  '// Cache our last path part for comparison when createMissingFolders = True.
  var lastPart as String = if( parts.LastIndex >= 0, parts(parts.LastIndex), "" )
  
  '// Attempt to determine if the target is a file. Obviously just a guess based
  '   on the last path part.
  var lastPartIsFile as Boolean = lastPart.Contains( "." )
  
  '// Iterate over all of the parts to build our relative path.
  for each part as String in parts
    '// Make sure the current path part isn't an empty string.
    if part.Trim.IsEmpty then Return nil
    
    '// Make sure that the current path part would result in a valid
    '   FolderItem.
    if current.Child( part ) is nil then Return nil
    
    '// If createMissingFolders is True, we'll create what we can.
    if createMissingFolders and current.Child( part ).Exists = False and (part <> lastPart or lastPartIsFile = False) then
      current.Child( part ).CreateFolder
    end if
    
    '// Update our variable.
    current = current.Child( part )
  next
  
  '// If we get here, we should have a valid FolderItem.
  Return current
End Function

The Test

Here’s a test case I’m using. Change the boolean parameter on line 3 to control whether missing folders are created automatically. Note that this test is designed for my system and you should change the array values to better suit your own filesystem and expectations.

'// An array of test paths. These resolve in a way I expect
'   on my system, but will not resolve the same way on others'
'   systems.
var relativePaths() as String = Array( "/", "/test.js", "/pdf tests/202506047.pdf", "/does not exist/will be created/", "/does not exist/somefile.png" )

'// Loop through the paths.
for each path as String in relativePaths
  '// Attempt to resolve the current path array item.
  '   Note that we're passing True to the createMissingFolders
  '   parameter. The test results will differ
  '   based on this value, and folders may need to be
  '   deleted for subsequent tests.
  var result as FolderItem = SpecialFolder.Desktop.ChildRelative( path, true )
  if result is nil then
    '// This normally occurs when createMissingFolders = False
    '   and your relative path is a child of a non-existent
    '   folder. Shouldn't be encountered when
    '   createMissingFolders = True.
    System.DebugLog( path + " is Nil" )
  elseif result.Exists = False then
    System.DebugLog( path + " does not exist" )
  else
    '// Success!
    System.DebugLog( result.NativePath )
  end if
next

quit
5 Likes