macoshomebrewhomebrew-cask

List up my apps installable with brew-cask


I want to know which of my apps are available to install with brew cask command.
How can I do it?


Specification
What I want to do is to extract apps that are also available on brew-cask from all apps in /Applications and list up their package-names.

# /Applications
Alfred 4.app
App Store.app
AppCleaner.app
Automator.app
Be Focused Pro.app
BetterTouchTool.app
Bitdefender
Bluetooth Explorer.app
Books.app
Calculator.app
Calendar.app
CheatSheet.app
Chess.app
Clipy.app
...
# package names of apps available on brew-cask
alfred
appcleaner
bettertouchtool
calibre
cheatsheet
clip
...

Solution

  • This is possible using Homebrew’s JSON API as well as some jq magic (brew install jq).

    1. Assuming none of your .app filenames contain a newline (very unlikely), you can get the list as a JSON array with a command combining ls and jq. However since we’ll use that list as a lookup it’s better to create an object instead:

      ls /Applications \
      | \grep '\.app$' \
      | jq -Rsc 'split("\n")[:-1]|map({(.):1})|add'
      

      This creates an object with each app as a key and 1 as a value (the value has no importance here). It outputs something like:

      {"1Password 7.app":1,"Amphetamine.app":1, "Firefox.app":1, …}
      
    2. You can list all 3,500+ installable casks using brew search --casks. In order to get a JSON describing one or more cask(s), including the .app they install, you can use brew cask info --json=v1 <cask> ….

      Combining these two, we can get a huge JSON describing all installable casks with:

      brew search --casks '' \
      | xargs brew info --cask --json=v2 \
      > allcasks.json
      

      This command takes ~10s on my machine so saving it in a file is a good idea.

    3. We can now filter this list to extract only the casks that install .apps from our earlier list:

      cat allcasks.json \
      | jq -r --argjson list '{…the list…}' '.[]|.[]|(.artifacts|map(.[]?|select(type=="string")|select(in($list)))|first) as $app|select($app)|"\(.token): \($app)"'
      

      Replace {…the list…} with the object we created earlier.

      This prints something like:

      1password: 1Password 7.app
      firefox: Firefox.app
      google-chrome: Google Chrome.app
      …
      

    If you feel adventurous, here is a one-liner that does all these commands at once:

    brew search --casks '' \
    |xargs brew info --cask --json=v2 \
    |jq -r --argjson l "$(ls /Applications|\grep '\.app$'|jq -Rsc 'split("\n")[:-1]|map({(.):1})|add')" '.[]|.[]|(.artifacts|map(.[]?|select(type=="string")|select(in($l)))|first) as $a|select($a)|"\(.token): \($a)"'
    

    Breakdown of the jq command:

    .[] # flatten the list
     |  # then for each element:
     .[] # flatten the list
     |  # then for each element:
       ( # take its artifacts
         .artifacts
          # then for each one of them
          | map(
             # take only arrays
             .[]?
             # select their string elements
             | select(type=="string")
             # that are also in the list
             | select(in($list)
           )
       )
       # take the first matching artifact
       | first)
       # and store it in $app
       as $app
     # then take only the elements with a non-empty $app
     | select($app)
     # and print their name (.token) and the app ($app)
     |"\(.token): \($app)"