TITLE: Shell script to extract colours from macOS Terminal.app
themes
DATE: 2022-12-29
AUTHOR: John L. Godlee
====================================================================


On macOS, configuration profiles for Terminal.app are stored as
plist files with the extension .terminal. These files contain XML
or binary data. The profiles contain, among other settings, the
colour scheme for the terminal.

I wrote a shell script to extract the colour scheme information as
RGB255 values for each colour scheme value. The function uses a
combination of xmlstarlet, awk, and some programs which come with
macOS, like tr, base64, and plutil.

 [xmlstarlet]: https://xmlstar.sourceforge.net/
 [awk]: https://www.gnu.org/software/gawk/manual/gawk.html

First, I define a function to extract colour values:

   function dict_search() {
       # If colour code found in file
       if grep -q "${1}" "${2}"; then
           xml sel --net -t -v
"//key[.=\"$1\"]/following-sibling::data[1]" "${2}" |
               tr -d '[:space:]' |
               base64 --decode |
               plutil -convert xml1 - -o - |
               xml sel --net -t -v
"//dict/array/dict/key[.='NSRGB']/following-sibling::data[1]" |
               tr -d '[:space:]' |
               base64 --decode |
               awk -v var="${1}" 'BEGIN{ ORS=" "; print var }
                       { for(i=1;i<=3;i++) { printf "%.0f ",
$i*255 }; printf "\n" }'
       fi
   }

Going line by line:

-   if grep ... will only run the rest of the function if the
chosen colour ID is found in the .terminal file.
-   xml sel ... searches for the key (i.e. the chosen colour ID),
then pulls the contents of the data node which comes directly after
it.
-   tr -d ... removes all spaces, including new lines, effectively
concatenating the contents of the node.
-   base64 ... converts the base64 encoded data into text
-   plutil -convert ... converts that into XML
-   xml sel ... extracts the RGB values in the XML
-   tr -d ... as above, removes all spaces and new lines
-   base64 ... as above, converts base64 encoded data into text
-   awk ... prints the name of the colour ID, and the three RGB
components multiplied by 255 and rounded to convert from a 0-1
decimal to a 0-255 integer.

Then I create an array containing all the colour IDs of interest:

   colArray=("ANSIBlackColor" "ANSIBlueColor" "ANSICyanColor"
"ANSIGreenColor" "ANSIMagentaColor" "ANSIRedColor" "ANSIWhiteColor"
"ANSIYellowColor" "ANSIBrightBlackColor" "ANSIBrightBlueColor"
"ANSIBrightCyanColor" "ANSIBrightGreenColor"
"ANSIBrightMagentaColor" "ANSIBrightRedColor"
"ANSIBrightWhiteColor" "ANSIBrightYellowColor" "BackgroundColor"
"CursorColor" "SelectionColor" "TextBoldColor" "TextColor")

Finally, to construct the output of the script, firstly printing
the filename, then running the function for each colour ID, and
only printing the result to stdout if the function output is not
empty:

   # Print filepath of theme
   printf "%s\n" "${1}"

   # For each in array, run function
   for i in ${colArray[@]}; do
       # Run function
       out=$(dict_search "${i}" "${1}")

       # If output not empty, print
       if [ -n "${out}" ]; then
           printf "%s\n" "${out}"
       fi
   done

This produces, for example for my tweaked version of the Smyck
colour scheme, which I use every day:

 [Smyck]: https://color.smyck.org/

   smyck.terminal
   ANSIBlackColor 0 0 0
   ANSIBlueColor 64 125 153
   ANSICyanColor 32 115 131
   ANSIGreenColor 125 169 0
   ANSIMagentaColor 186 138 204
   ANSIRedColor 184 65 49
   ANSIWhiteColor 161 161 161
   ANSIYellowColor 196 165 7
   ANSIBrightBlackColor 75 75 75
   ANSIBrightBlueColor 141 207 240
   ANSIBrightCyanColor 106 217 207
   ANSIBrightGreenColor 196 241 55
   ANSIBrightMagentaColor 247 154 255
   ANSIBrightRedColor 214 131 124
   ANSIBrightWhiteColor 247 247 247
   ANSIBrightYellowColor 254 225 77
   BackgroundColor 18 21 28
   CursorColor 32 115 130
   SelectionColor 32 115 131
   TextBoldColor 247 247 247
   TextColor 247 247 247

 ![Smyck colour
scheme.](https://johngodlee.xyz/img_full/terminal_colours/smyck.png)

 ![Smyck colour scheme in the
terminal.](https://johngodlee.xyz/img_full/terminal_colours/smyck_te
rm.png)