Benjamin Sago / ogham / cairnrefinery / etc…

Technical notes Maintain macOS file associations with Specsheet

I am very particular about my file associations. Text files should always open in my main text editor, media files should always open in my media player, disk images should always open with FastDMG, that sort of thing. I find it very annoying when I install a new text editor to try it out, only to find it’s hijacked all the file associations for every progamming language it knows about.

I don’t think there’s a way to stop applications doing this — there’s no confirmation popup like there is for setting the default browser. So I’m forced to use SwiftDefaultApps to change it whenever it happens (though I sneakily will continue to use RCDefaultApps for as long as I am able). You can also use the lsregister command-line tool to change the database.

The best I can do to avoid this problem is with a script to the extension mappings are all correct, so after I install a new editor, or whenever something opens with the wrong program, I only have to change the erroneous entries instead of having to go through and search every file type I want to maintain.

My script is written as a Specsheet document that repeatedly calls duti, which prints the default application for a Uniform Type Identifier (UTI) on macOS.

The script is basically a longer version of this:

spec/associations.toml
cmd = [

  # Pictures
  { shell = 'duti -x arw',  stdout = { string = 'Preview' } },

  # Media files
  { shell = 'duti -x aac',  stdout = { string = 'IINA' } },
  { shell = 'duti -x m4a',  stdout = { string = 'IINA' } },
  { shell = 'duti -x mkv',  stdout = { string = 'IINA' } },
  { shell = 'duti -x mp3',  stdout = { string = 'IINA' } },

  # Text files
  { shell = 'duti -x log',  stdout = { string = 'BBEdit' } },
  { shell = 'duti -x txt',  stdout = { string = 'BBEdit' } },
  { shell = 'duti -x md',   stdout = { string = 'BBEdit' } },
]

Using the short-form TOML syntax, each Specsheet check runs the duti binary (which is available from Homebrew) and asserts that the output from the command contains the correct application name. Running it is as simple as passing the file to specsheet, optionally with the -shide parameter to hide the ones that are already correct:

$ specsheet associations.toml

   associations.toml
 ✔︎ Command ‘duti -x arw’ executes with stdout containing ‘Preview’
 ✔︎ Command ‘duti -x aac’ executes with stdout containing ‘IINA’
 ✔︎ Command ‘duti -x m4a’ executes with stdout containing ‘IINA’
 ✔︎ Command ‘duti -x mkv’ executes with stdout containing ‘IINA’
 ✔︎ Command ‘duti -x mp3’ executes with stdout containing ‘IINA’
 ✔︎ Command ‘duti -x log’ executes with stdout containing ‘BBEdit’
 ✔︎ Command ‘duti -x txt’ executes with stdout containing ‘BBEdit’
 ✔︎ Command ‘duti -x md’ executes with stdout containing ‘BBEdit’
   8/8 successful

Now, if I install something that clobbers my file associations, I’ll know.