Skip to content

Instantly share code, notes, and snippets.

@subtleGradient
Last active May 14, 2024 16:24
Show Gist options
  • Save subtleGradient/4dae70da87128229210afcf47f4341ff to your computer and use it in GitHub Desktop.
Save subtleGradient/4dae70da87128229210afcf47f4341ff to your computer and use it in GitHub Desktop.
Bun.sh script to add a new LSItemContentType to ~/Library/QuickLook/QLStephen.qlgenerator/Contents/Info.plist for any file path
#!/usr/bin/env bun
import { $, BunFile, file } from "bun"
type CFBundleDocumentType = {
CFBundleTypeName: "Content"
CFBundleTypeRole: "QLGenerator"
LSItemContentTypes: ["public.data", "public.content", ...string[]]
LSTypeIsPackage: false
NSPersistentStoreTypeKey: "XML"
}
type InfoPlist = {
CFBundleExecutable: "QLStephen"
CFBundleDocumentTypes: [CFBundleDocumentType]
}
type mdlsOutput = {
_kMDItemDisplayNameWithExtensions: unknown
kMDItemContentCreationDate: unknown
kMDItemContentCreationDate_Ranking: unknown
kMDItemContentModificationDate: unknown
kMDItemContentType: string
kMDItemContentTypeTree: string[]
kMDItemDateAdded: unknown
kMDItemDisplayName: unknown
kMDItemDocumentIdentifier: unknown
kMDItemFSContentChangeDate: unknown
kMDItemFSCreationDate: unknown
kMDItemFSCreatorCode: unknown
kMDItemFSFinderFlags: unknown
kMDItemFSInvisible: unknown
kMDItemFSIsExtensionHidden: unknown
kMDItemFSLabel: unknown
kMDItemFSName: unknown
kMDItemFSOwnerGroupID: unknown
kMDItemFSOwnerUserID: unknown
kMDItemFSSize: unknown
kMDItemFSTypeCode: unknown
kMDItemInterestingDate_Ranking: unknown
kMDItemKind: unknown
kMDItemLogicalSize: unknown
kMDItemPhysicalSize: unknown
}
const PlistAsJSON = {
read: async (plistPath: string | BunFile): Promise<InfoPlist> =>
await $`cat < ${plistPath} | plutil -convert json -o - -`.json(),
write: async (plistPath: string | BunFile, info: InfoPlist) =>
$`echo ${JSON.stringify(info)} | plutil -convert xml1 -o ${plistPath} -`,
}
const mdls = async <N extends keyof mdlsOutput>(theFile: BunFile, name: N): Promise<Pick<mdlsOutput, N>> =>
await $`mdls -plist - -name ${name} ${theFile} | plutil -convert json -o - -`.json()
const refresh_ql_cache = async () => await $`qlmanage -r; qlmanage -r cache; killall Finder; killall -9 QuickLookSatellite`
const QLStephen$Info_plist = {
file: file(`${process.env.HOME!}/Library/QuickLook/QLStephen.qlgenerator/Contents/Info.plist`),
__cache__: null as null | InfoPlist,
async json() {
return (QLStephen$Info_plist.__cache__ ??= await PlistAsJSON.read(QLStephen$Info_plist.file))
},
async write() {
const info = await QLStephen$Info_plist.json()
QLStephen$Info_plist.__cache__ = null
return PlistAsJSON.write(QLStephen$Info_plist.file, info)
},
LSItemContentTypes: {
read: async () => (await QLStephen$Info_plist.json()).CFBundleDocumentTypes[0].LSItemContentTypes,
push: async (newLSItemContentType: string) => {
const info = await QLStephen$Info_plist.json()
// abort if already added
if (info.CFBundleDocumentTypes[0].LSItemContentTypes.includes(newLSItemContentType))
throw new Error(`Already added: ${newLSItemContentType}`)
info.CFBundleDocumentTypes[0].LSItemContentTypes.push(newLSItemContentType)
return QLStephen$Info_plist.write()
},
},
}
type PluginInfo = {
path: string
version?: string
loaded?: boolean
}
type QuickLookData = {
serverInfo: string
memoryUsage: string
descriptors: string
lastBurst: string
plugins: Record<string, PluginInfo>
}
function parseQuickLookOutput(output: string): QuickLookData {
const lines = output.split("\n")
const plugins: Record<string, PluginInfo> = {}
let serverInfo = ""
let memoryUsage = ""
let descriptors = ""
let lastBurst = ""
lines.forEach(line => {
if (line.startsWith("server:")) {
serverInfo = line
} else if (line.startsWith("memory used:")) {
memoryUsage = line
} else if (line.includes("used descriptors:")) {
descriptors = line
} else if (line.startsWith("last burst:")) {
lastBurst = line
} else if (line.startsWith(" ")) {
// Plugin lines are indented
const match = line.trim().match(/^([^ ]+) -> (.+?)( \(([^)]+)\))?$/)
if (match) {
const [_, type, path, __, versionDetail] = match
const loaded = versionDetail?.includes("loaded")
const version = versionDetail?.replace(" - loaded", "") || undefined
plugins[type] = { path, version, loaded: loaded || undefined }
}
}
})
return {
serverInfo,
memoryUsage,
descriptors,
lastBurst,
plugins,
}
}
type GroupedPlugins = Record<string, Array<{ type: string; version?: string; loaded?: boolean }>>
function groupPluginsByPath(data: QuickLookData): GroupedPlugins {
const grouped: GroupedPlugins = {}
for (const [type, { path, version, loaded }] of Object.entries(data.plugins)) {
if (!grouped[path]) {
grouped[path] = []
}
grouped[path].push({ type, version, loaded })
}
return grouped
}
function getPluginsByType(data: QuickLookData, types: string[]): Record<string, PluginInfo> {
const filteredPlugins: Record<string, PluginInfo> = {}
types.forEach(type => {
if (data.plugins[type]) {
filteredPlugins[type] = data.plugins[type]
}
})
return filteredPlugins
}
// const skdjfhsdkhj = groupPluginsByPath(parseQuickLookOutput(await $`qlmanage -m`.text()))
// console.log(skdjfhsdkhj)
// const skdjfhsdkjh = getPluginsByType(
// parseQuickLookOutput(await $`qlmanage -m`.text()),
// (await mdls(file(__filename), "kMDItemContentTypeTree")).kMDItemContentTypeTree,
// )
// console.log(skdjfhsdkjh)
// if (skdjfhsdkhj) process.exit(1) // DEBUG
// Main function to add QuickLook support for a file type
async function addQuickLookSupport(filePath: string) {
const theFile = file(filePath)
if (!(await theFile.exists())) throw new Error(`File not found: ${filePath}`)
const { kMDItemContentType } = await mdls(theFile, "kMDItemContentType")
console.log(`Content type of ${filePath}: ${kMDItemContentType}`)
if (!kMDItemContentType) throw new Error(`Could not determine the content type of ${filePath}`)
try {
await QLStephen$Info_plist.LSItemContentTypes.push(kMDItemContentType)
} catch (error) {
if (error.message.startsWith("Already added:")) {
console.log(`QuickLook support already added for ${filePath} ${kMDItemContentType}`)
return process.exit(0)
}
throw error
}
if ((await QLStephen$Info_plist.LSItemContentTypes.read()).includes(kMDItemContentType)) await refresh_ql_cache()
else throw new Error(`Failed to add QuickLook support for ${filePath} ${kMDItemContentType}`)
console.log(`Added QuickLook support for ${filePath} ${kMDItemContentType}`)
}
// Get the file path from the command line arguments and run the main function
const filePath = process.argv[2]
await addQuickLookSupport(filePath)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment