Skip to content

Instantly share code, notes, and snippets.

@Clustmart
Last active July 14, 2024 11:52
Show Gist options
  • Save Clustmart/707fe005fecc1efcb7fe51b3ff8d1e42 to your computer and use it in GitHub Desktop.
Save Clustmart/707fe005fecc1efcb7fe51b3ff8d1e42 to your computer and use it in GitHub Desktop.
Export Safari bookmarks and folder structure to ~/bookmarks.csv
import Foundation
// Define the file path to save the CSV
let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("bookmarks.csv")
// Create the CSV file if it doesn't exist
if !FileManager.default.fileExists(atPath: filePath.path) {
FileManager.default.createFile(atPath: filePath.path, contents: nil, attributes: nil)
}
// Open the file handle for writing
guard let fileHandle = try? FileHandle(forWritingTo: filePath) else {
print("Failed to open file for writing.")
exit(1)
}
defer {
fileHandle.closeFile()
}
// Write the CSV headers
let headers = "Website name,URL,Bookmark path\n"
fileHandle.write(headers.data(using: .utf8)!)
// Get all bookmarks
let bookmarksPlistPath = "\(NSHomeDirectory())/Library/Safari/Bookmarks.plist"
let bookmarksData = try! Data(contentsOf: URL(fileURLWithPath: bookmarksPlistPath))
let plist = try! PropertyListSerialization.propertyList(from: bookmarksData, options: [], format: nil)
// Recursive function to get all bookmarks with their parent folders
func getAllBookmarks(bookmarks: [[String: Any]], parentFolders: [String]) -> [[String]] {
var results: [[String]] = []
for bookmark in bookmarks {
if let url = bookmark["URLString"] as? String,
let uriDictionary = bookmark["URIDictionary"] as? [String: Any],
let name = uriDictionary["title"] as? String {
let bookmarkPath = parentFolders.joined(separator: " > ")
results.append([name, url, bookmarkPath])
}
if let children = bookmark["Children"] as? [[String: Any]] {
let newParentFolders = parentFolders + [bookmark["Title"] as! String]
results += getAllBookmarks(bookmarks: children, parentFolders: newParentFolders)
}
}
return results
}
// Call the recursive function to get all bookmarks with their parent folders
let allBookmarks = getAllBookmarks(bookmarks: (plist as! NSDictionary)["Children"] as! [[String: Any]], parentFolders: [])
// Write the bookmarks to the CSV
for bookmark in allBookmarks {
let line = "\(bookmark[0]),\(bookmark[1]),\(bookmark[2])\n"
fileHandle.write(line.data(using: .utf8)!)
}
print("Bookmarks exported to: \(filePath)")
@marceldarvas
Copy link

I gave this a try but I am not sure what went wrong:

./export-safari-bookmarks.swift: line 3: //: is a directory
./export-safari-bookmarks.swift: line 4: syntax error near unexpected token `('
./export-safari-bookmarks.swift: line 4: `let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("bookmarks.csv")'

@Clustmart
Copy link
Author

Thank you for taking the time to try out the code. It appears that the program required an existing bookmarks.csv file, which might have caused some confusion. I have addressed this issue and updated the code to create the file automatically if it doesn't exist. This modification should eliminate the need for a pre-existing bookmarks.csv file.

@marceldarvas
Copy link

Thank you for the response! I have tried the new code, but ran into similar issues

./export-safari-bookmarks-new.swift
./export-safari-bookmarks-new.swift: line 1: import: command not found
./export-safari-bookmarks-new.swift: line 3: //: is a directory
./export-safari-bookmarks-new.swift: line 4: syntax error near unexpected token `('
./export-safari-bookmarks-new.swift: line 4: `let filePath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("bookmarks.csv")'

I actually had the import command not found issue, before as well.

I realized that maybe this script was not meant as an executable file, so I tried running it with
swift export-safari-bookmarks-new.swift
which got me permission error, so I tried with sudo but that messed with NSHomeDirectory().

I don't have any prior experience with Swift, can you please share some instructions for your workflow?

@Clustmart
Copy link
Author

I couldn't replicate your issue. The error import: command not found indicates that the import statement is being interpreted as a shell command rather than a valid Swift language construct. This typically happens when you try to execute a Swift script using a shell interpreter.
You should make sure that swift is installed (execute swift --version). Executing swift export-safari-bookmarks-new.swift is correct, it works on my system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment