POSIX path convertor?

Before I reinvent the wheel, does anyone have a method that is able to standardise a Windows file path to a POSIX (i.e. forward-slash separated) one and vice versa?

For my ObjoScript language, I’m implementing file handling. Since it’s cross-platform, I would like all paths passed to ObjoScript’s file system class to be in POSIX format. Behind the scenes, this uses FolderItem to manipulate the host’s file system.

If the interpreter is running on Linux or macOS then there is no conversion - this would work:

var file = FSItem("some/POSIX/path.txt")

But on Windows that would fail because I should be passing "some\POSIX\path.txt" to FolderItem.

Has anyone already implemented a method to do this before I attack it and try to catch the edge cases?

Not me, though I was challenged with adding such a feature to FindAnyFile just a week ago (didn’t do it for it being not that easy). The challenge is that referring to the right volume may be difficult. As long as it’s all on the boot volume, it might be easier.
Also be aware that there’s two types used on Win: Classic DOS, like “C:\Users\user\Desktop” and UNC (“\\user\Desktop”). UNC may be the smarter choice. See also: File path formats on Windows systems | Microsoft Learn
And if you can limit your paths to relative ones (e.g. relativ to the project folder), then you might have it much easier, too. Replacing / with \ may all you need in that case, by using DOS paths.

Oh, you will have to replace or drop some more illegal chars (colon, double quote and some more - google it) on the windows side, though. I suggest you only do that at runtime on the Windows side, and leave the chars in your stored string, so that they remain valid when used on macOS.

If you need to make relativ paths, I have a function for that, see below.

Function RelativePOSIXPathTo(extends fromFile as FolderItem, toFile as FolderItem) As String
  // returns the full path to toFile if a relative path can't be created
  // based on code provided by Jonathan Johnson

  const kUpDirPath = ".."
  const kSameDirPath = "."
  const pathSeparator = "/"
  
  // Get a list of path parts by looping until the parent is nil.
  dim tmpf as FolderItem
  dim fromPathParts() as String
  dim toPathParts() as String
  
  tmpf = fromFile
  while tmpf <> nil
    fromPathParts.Append tmpf.Name
    tmpf = tmpf.Parent
  wend
  
  tmpf = toFile
  while tmpf <> nil
    toPathParts.Append tmpf.Name
    tmpf = tmpf.Parent
  wend
  
  // If the top-level items don't match, we're on different volumes, and that ain't going to work
  if fromPathParts (fromPathParts.Ubound) <> toPathParts (toPathParts.Ubound) then
    return toFile.POSIXPath
  end if
  
  // Now, remove each of the common items from the tops of both arrays.
  while fromPathParts.Ubound >= 0 and toPathParts.Ubound >= 0 _
    and fromPathParts(fromPathParts.Ubound) = toPathParts(toPathParts.Ubound)
    fromPathParts.Remove(fromPathParts.Ubound)
    toPathParts.Remove(toPathParts.Ubound)
  wend
  
  // Now, for every "from" path part that's remaining, it's one level we must go up.
  
  dim relativePathParts() as String
  dim i as integer
  
  if fromPathParts.Ubound < 0 then
    // Just operate on the current directory
    // !TT not necessary: relativePathParts.Append kSameDirPath
  else
    for i = 0 to fromPathParts.Ubound
      relativePathParts.Append kUpDirPath
    next
  end if
  
  // Now that we're up to the same parent folder, we need to traverse down into
  // each "toPathPart"
  while toPathParts.Ubound >= 0
    relativePathParts.Append toPathParts.Pop
  wend
  
  // Finally, return the result
  return Join (relativePathParts, pathSeparator)
End Function
```
1 Like

That’s really helpful Thomas, thank you.

Spent the last couple of hours on this - it is very complex isn’t it with a lot of edge cases!

1 Like

I know. I had the same troubles with resolving the multiple refs for external files in Arbed when reading a Xojo project (where multiple paths are stored for the various platforms, including a Finder Alias, and I need to resolve them all to check if any are pointing to the right file).

1 Like

I set a kPathSeparator variable for platforms to “/” or “\” as needed. I then do a ReplaceAllB based on the platform coming from / going to.

If coming from Windows to POSIX, I preface the line with “/” and drop the “:”

So:

C:\Users\Tim\Documents

Becomes:

/c/Users/Tim/Documents

If moving from POSIX to Windows, I drop the leading “/” and ask the user for the Drive letter and then swap the kPathSeparator using ReplaceAllB.

So:

/home/tjones/Documents

Becomes (assuming the user has chosen F: as the drive):

F:\home\tjones\Documents

Of course, your handling of the drive letter is left to you to choose.

1 Like