How to escape a macOS filesystem URL in Swift? - swift

Imagine I want to:
ask the user to enter a file path as a string (which could be something such as 'My' "a/b" folder which in macOS is an entirely acceptable file name.)
create an absolute URL based on the string entered in step 1 eg /Users/john/Desktop/'My' "a/b" folder
do something in a Process with that URL, for example run /bin/mkdir "/Users/john/Desktop/'My' \"a:b\" folder"*
I'm not sure how to achieve this in Swift. I've searched the URL docs and not seen anything to do with macOS's filesystem escaping. I am sure there must be a canonical method to achieve this, and not a series of hopefully-I-covered-the-edge-cases string replacements.
let pathString = """
/Users/john/Desktop/'My' "a/b" folder
"""
let url = URL(fileURLWithPath: pathString)
let urlString = "\"" + url.path + "\""
var process = Process()
process.launchPath = "/bin/mkdir"
process.arguments = [urlString]
I would like urlString to be something like "/Users/john/Desktop/'My' \"a:b\" folder". With that I could create a command such as:
mkdir "/Users/john/Desktop/\'My\'\ \"a\:b\"\ folder"
This is a hypothetical example that illustrates the issue. Of course I'm not actually trying to create a directory using mkdir from within Swift :)

Can you please try this
.addingPercentEncoding(withAllowedCharacters: .urlUserAllowed)

Related

URL(filePath: strPath) vs URL(fileURLWithPath: strPath)

let url = URL(filePath: pathString!)
let url2 = URL(fileURLWithPath: pathString!)
I tried both and they worked both.
What's the difference between the two ways creating such an URL-object?
String file paths are outmoded. Use file URLs. And not file URLs derived from a string file path; construct or obtain the URL legitimately. So the real answer is: don't use either of those methods.

Access root file created by script Swift

From this question: How can I get all image names in asset catalog group?
I want to access a file created in the root of an App in Swift.
My App is called Challenge and my script is
# Type a script or drag a script file from your workspace to insert its path.
#!/bin/sh
>./Challenge/fileNames.txt
for FILE in ./Challenge/Images.xcassets/actions/*; do
echo $FILE >> ./Challenge/fileNames.txt
done
I have tried
let fileManager = FileManager.default
let imagePath = Bundle.main.resourcePath! + "fileNames.txt"
let imageNames = try! fileManager.contentsOfDirectory(atPath: imagePath)
And many other combinations .
This is not homework, I have tried various solutions but as I am not sure where the file is within the file system (how the root can be accessed within the App) I seem unable to locate the file.
Question: How to read ./Challenge/fileNames.txt from an App called challenger, created by a script during the build phase.
The file is located in the main bundle folder, not resource path:
let fileManager = FileManager.default
let imagePath = Bundle.main.path(forResource: "fileName", ofType: "txt")
let content = try! String(contentsOfFile: imagePath)

"Bash like" paths to NSURL or Foundation path

In most command line tools, paths to files can be specified using formats like those: ../someFile, ~/anotherFile, /foo/bar. How can I init a valid Swift URL or (Foundation) path from such a path?
EDIT:
Maybe a code example is clearer, say I want to init a String from path and I have a foo file in my user directory, doing this:
try String(contentsOfFile: "~/foo")
Doesn't work (error is file does not exist)
In Swift, you can do :
let str = "~/Documents"
// NSString has a function for decoding this, not available to String:
let str2 = (str as NSString).standardizingPath as String
// Swift deprecated the String path manipulation functions,
// but supplies them as part of NSURL:
let url = URL(fileURLWithPath: str)
let url2 = url.standardizedFileURL // Expands the URL

How do I parse out only the files actual name and not the full path?

I have some code that (I thought) could split a filename from its file type. For example, "filename.txt" -> ["filename, "txt"].
However, I didn't realize that when I use a dialog box to open a file name, I get the full path (rookie mistake, I know). For example, this is what my function is returning:
filename_array: ["/home/user/Downloads/filename", "txt"]
How do I remove the path stuff and only return the filename part?
Apple has been trying to get rid of all the "path as string" style and migrate them into URL. The path manipulation API has been removed from String and moved to URL:
let path = "/home/user/Downloads/filename.txt"
let url = URL(fileURLWithPath: path)
let fileNameOnly = url.deletingPathExtension().lastPathComponent // filename
let fileExtention = url.pathExtension // txt
Or you can do it old-school style with NSString:
let path = "/home/user/Downloads/filename.txt"
let fileName = (path as NSString).lastPathComponent as NSString // filename.txt
let fileNameOnly = fileName.deletingPathExtension // filename
let fileExtension = fileName.pathExtension // txt

Get path to Swift script from within script

I'm writing a script in Swift, and I want it to modify some files that always exist in the same directory as the script itself. Is there a way to get the path to the script from within itself? I tried:
print(Process.arguments)
But that outputs only the path that was actually given to the script, which may be the fully resolved path, just the file name, or anything in between.
I'm intending the script to be run with swift /path/to/my/script.swift.
The accepted answer doesn't work in Swift 3. Also, this is a more straightforward approach:
import Foundation
let currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
let url = URL(fileURLWithPath: CommandLine.arguments[1], relativeTo: currentDirectoryURL)
print("script at: " + url.path)
However, it has the same problem pointed out by #rob-napier, if the script's directory is in your PATH.
just in swift:
let cwd = FileManager.default.currentDirectoryPath
print("script run from:\n" + cwd)
let script = CommandLine.arguments[0];
print("\n\nfilepath given to script:\n" + script)
//get script working dir
if script.hasPrefix("/") { //absolute
let path = (script as NSString).deletingLastPathComponent
print("\n\nscript at:\n" + path)
} else {
let urlCwd = URL(fileURLWithPath: cwd)
if let path = URL(string: script, relativeTo: urlCwd)?.path {
let path = (path as NSString).deletingLastPathComponent
print("\n\nscript at:\n" + path)
}
}