|
| 1 | +#!/usr/bin/env bash |
| 2 | +#: Use this computer's shared-mime-info database to find missing MIME type |
| 3 | +#: icons in Papirus. This script can also be used to discover which icon |
| 4 | +#: would be used for a given MIME type, and why. |
| 5 | +#: |
| 6 | +#: usage: __SCRIPT__ [OPTIONS] |
| 7 | + |
| 8 | +set -euo pipefail |
| 9 | + |
| 10 | +SRCDIR=$(realpath "../Papirus") |
| 11 | +AVAILABLE_ICONS_CACHE="x-srcdir-icons" |
| 12 | +MIME_DIRS_CACHE="x-mimedirs" |
| 13 | +MIME_TYPES_CACHE="types" |
| 14 | +MIME_ICONS_CACHE="icons" |
| 15 | +MIME_GENERIC_ICONS_CACHE="generic-icons" |
| 16 | + |
| 17 | +# Command line option vars and default behaviour |
| 18 | +VERBOSITY=1 |
| 19 | +SHOW_HEADER=1 |
| 20 | +SCAN_PERSONAL_MIMEDIR=0 |
| 21 | + |
| 22 | + |
| 23 | +show_usage () { |
| 24 | + script=$(basename "$0") |
| 25 | + sed -Ee 's/^\s*#:\s?//p;d' "$0" | sed -Ee 's/__SCRIPT__/'"$script"'/' >&2 |
| 26 | +} |
| 27 | + |
| 28 | + |
| 29 | +# Get all MIME dirs on the system. |
| 30 | + |
| 31 | +get_mime_dirs () { |
| 32 | + local data_dirs=${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/} |
| 33 | + if [[ $SCAN_PERSONAL_MIMEDIR -gt 0 ]]; then |
| 34 | + data_dirs="${XDG_DATA_HOME:-$HOME/.local/share}:${data_dirs}" |
| 35 | + fi |
| 36 | + local IFS=: |
| 37 | + for d in $data_dirs; do |
| 38 | + local m="$d/mime" |
| 39 | + [ -d "$m" ] || continue |
| 40 | + printf "%s\n" "$m" |
| 41 | + done |
| 42 | +} |
| 43 | + |
| 44 | + |
| 45 | +# Gathers a list of all available icon names in $SRCDIR. |
| 46 | +# This is not limited to just icons in the mimetypes subfolders, |
| 47 | +# because explicit <icon/> and <generic-icon/> dirctives |
| 48 | +# may name icons in any context. |
| 49 | + |
| 50 | +get_available_icons () { |
| 51 | + find "$SRCDIR/"*'x'* \( -type f -o -type l \) -name '*.svg' \ |
| 52 | + | sed -e 's!.*/!!;s![.]svg$!!' \ |
| 53 | + | sort | uniq |
| 54 | +} |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | +# Collects a line-oriented cache file from all shared-mime-info MIME dirs. |
| 59 | +# Expects to be run inside the working tempdir. |
| 60 | + |
| 61 | +get_mimedirs_cache () { |
| 62 | + local cache="$1" |
| 63 | + while read -r d; do |
| 64 | + local c="$d/$cache" |
| 65 | + [ -f "$c" ] || continue |
| 66 | + cat "$c" |
| 67 | + done < "$MIME_DIRS_CACHE" | sort | uniq |
| 68 | +} |
| 69 | + |
| 70 | + |
| 71 | + |
| 72 | +# Display a header for the lines check_mimetype outputs. |
| 73 | + |
| 74 | +show_header () { |
| 75 | + ((SHOW_HEADER)) || return 0 |
| 76 | + cat <<"__END_HEADER__" |
| 77 | +┌─────── E=Exact icon found, as named in the mimetype’s <icon/> directive. |
| 78 | +│┌────── e=Exact icon found using the default “replace / with -” strategy. |
| 79 | +││┌───── G=Generic icon found, as named in the mimetype’s <generic-icon/>. |
| 80 | +│││┌──── g=Generic icon found using the default “<type>/x-generic” strategy. |
| 81 | +││││ ┌── Match quality (filtered by --output-level). |
| 82 | +││││ │ ┌ MIME type + any named matches from E and G. |
| 83 | +││││ │ │ |
| 84 | +__END_HEADER__ |
| 85 | +} |
| 86 | + |
| 87 | + |
| 88 | +# Check one MIME type has a valid icon in the theme. |
| 89 | +# Expects to be run inside the working tempdir. |
| 90 | + |
| 91 | +check_mimetype () { |
| 92 | + local mimetype="$1" |
| 93 | + local mimetype_re |
| 94 | + mimetype_re=$(sed -Ee 's!(\W)!\\\1!g' <<< "$mimetype") |
| 95 | + |
| 96 | + local icon |
| 97 | + local match_quality |
| 98 | + local named_matches="" |
| 99 | + local flag_char |
| 100 | + local flags_str="" |
| 101 | + local color |
| 102 | + |
| 103 | + # First possibility is that it has an explicit icon override |
| 104 | + # defined in MIMEDIRS/icons. This falls through to the tests below |
| 105 | + # if there's no icon, or if there's no override defined. |
| 106 | + flag_char="-" |
| 107 | + icon=$(sed -Ee "s#^${mimetype_re}:##g; tL; d; :L q" "$MIME_ICONS_CACHE") |
| 108 | + if [ -n "$icon" ]; then |
| 109 | + if grep -q -x -F "$icon" -- "$AVAILABLE_ICONS_CACHE"; then |
| 110 | + match_quality=${match_quality:-4} # best |
| 111 | + [[ VERBOSITY -lt match_quality ]] && return 0 |
| 112 | + flag_char="E" |
| 113 | + color=${color:-2} # green |
| 114 | + named_matches="${named_matches}, ${icon}" |
| 115 | + fi |
| 116 | + fi |
| 117 | + flags_str="${flags_str}${flag_char}" |
| 118 | + |
| 119 | + # Second possibility is that we can form an icon name by |
| 120 | + # substituting "-" for "/" in the mimetype. |
| 121 | + flag_char="-" |
| 122 | + icon=$(sed -Ee 's#/#-#g' <<< "$mimetype") |
| 123 | + if grep -q -x -F "$icon" -- "$AVAILABLE_ICONS_CACHE"; then |
| 124 | + match_quality=${match_quality:-3} # good |
| 125 | + [[ VERBOSITY -lt match_quality ]] && return 0 |
| 126 | + flag_char="e" |
| 127 | + color=${color:-2} # green |
| 128 | + fi |
| 129 | + flags_str="${flags_str}${flag_char}" |
| 130 | + |
| 131 | + # Third chance for the mimetype is that there's a generic icon defined as |
| 132 | + # a fallback in MIMEDIRS/generic-icons. The syntax of these files is the |
| 133 | + # same as MIMEDIRS/icons |
| 134 | + flag_char="-" |
| 135 | + icon=$(sed -Ee "s#^${mimetype_re}:##g; tL; d; :L q" \ |
| 136 | + "$MIME_GENERIC_ICONS_CACHE") |
| 137 | + if [ -n "$icon" ]; then |
| 138 | + if grep -q -x -F "$icon" -- "$AVAILABLE_ICONS_CACHE"; then |
| 139 | + match_quality=${match_quality:-2} # reasonable |
| 140 | + [[ VERBOSITY -lt match_quality ]] && return 0 |
| 141 | + flag_char="G" |
| 142 | + color=${color:-3} # yellow |
| 143 | + named_matches="${named_matches}, ${icon}" |
| 144 | + fi |
| 145 | + fi |
| 146 | + flags_str="${flags_str}${flag_char}" |
| 147 | + |
| 148 | + # Final chance! |
| 149 | + # Try to form an icon name as if the subtype was "x-generic" instead. |
| 150 | + flag_char="-" |
| 151 | + icon=$(sed -Ee 's#/.*#-x-generic#g' <<< "$mimetype") |
| 152 | + if grep -q -x -F "$icon" -- "$AVAILABLE_ICONS_CACHE"; then |
| 153 | + match_quality=${match_quality:-1} # poor, but sometimes acceptable |
| 154 | + [[ VERBOSITY -lt match_quality ]] && return 0 |
| 155 | + flag_char="g" |
| 156 | + color=${color:-1} # red |
| 157 | + fi |
| 158 | + flags_str="${flags_str}${flag_char}" |
| 159 | + |
| 160 | + # Format the summary line. |
| 161 | + color=${color:-1} # red |
| 162 | + match_quality=${match_quality:-0} # worst |
| 163 | + if [[ VERBOSITY -ge match_quality ]]; then |
| 164 | + local style_on="" |
| 165 | + local style_off="" |
| 166 | + if [[ -t 1 ]]; then |
| 167 | + printf -v style_on '\033[3%dm' "$color" |
| 168 | + printf -v style_off '\033[0m' |
| 169 | + fi |
| 170 | + printf '%s%s %d %s' \ |
| 171 | + "$style_on" "$flags_str" "$match_quality" "$mimetype" |
| 172 | + if [[ -n $named_matches ]]; then |
| 173 | + printf ' → {%s}' \ |
| 174 | + "$(sed -Ee 's!^,\s*!!' <<< "$named_matches")" |
| 175 | + fi |
| 176 | + printf '%s\n' "$style_off" |
| 177 | + fi |
| 178 | +} |
| 179 | + |
| 180 | + |
| 181 | +# Show a summary of all known MIME types that don't have an icon in SRCDIR. |
| 182 | +# Expects to be run inside the working tempdir. |
| 183 | + |
| 184 | +find_missing_mimetype_icons () { |
| 185 | + # Start by caching lots of info |
| 186 | + get_mime_dirs > "$MIME_DIRS_CACHE" |
| 187 | + get_available_icons > "$AVAILABLE_ICONS_CACHE" |
| 188 | + for cache in \ |
| 189 | + "$MIME_ICONS_CACHE" "$MIME_TYPES_CACHE" \ |
| 190 | + "$MIME_GENERIC_ICONS_CACHE" |
| 191 | + do |
| 192 | + get_mimedirs_cache "$cache" > "./$cache" |
| 193 | + done |
| 194 | + |
| 195 | + show_header |
| 196 | + while read -r type; do |
| 197 | + check_mimetype "$type" </dev/null |
| 198 | + done < "$MIME_TYPES_CACHE" |
| 199 | +} |
| 200 | + |
| 201 | + |
| 202 | +# Convert a single Boolean argument to 0 or 1. |
| 203 | + |
| 204 | +boolify () { |
| 205 | + local arg |
| 206 | + arg=$(tr '[:lower:]' '[:upper:]' <<< "$1") |
| 207 | + case "$arg" in |
| 208 | + 1|TRUE|Y|YES) |
| 209 | + printf "%s\n" 1 |
| 210 | + return 0 |
| 211 | + ;; |
| 212 | + 0|FALSE|N|NO) |
| 213 | + printf "%s\n" 0 |
| 214 | + return 0 |
| 215 | + ;; |
| 216 | + esac |
| 217 | + printf >&2 "ERROR: %s\n" \ |
| 218 | + "boolean value must be 1/true/yes or 0/false/no" |
| 219 | + exit 2 |
| 220 | +} |
| 221 | + |
| 222 | + |
| 223 | +# Parse args and dispatch commands |
| 224 | + |
| 225 | +parse_args () { |
| 226 | + #: |
| 227 | + #: Options: |
| 228 | + #: |
| 229 | + local flag value |
| 230 | + while [ $# -gt 0 ]; do |
| 231 | + case "$1" in |
| 232 | + # The "--" symbol indicates no further options. |
| 233 | + --) |
| 234 | + shift |
| 235 | + break |
| 236 | + ;; |
| 237 | + #: --help, -h |
| 238 | + #: Show this help message, then exit. |
| 239 | + --help|-h) |
| 240 | + shift |
| 241 | + show_usage |
| 242 | + exit 0 |
| 243 | + ;; |
| 244 | + #: --output-level INT, -l INT (default: 1) |
| 245 | + #: How much output to show. Smaller values show less info, |
| 246 | + #: larger values show more info about successful lookups. |
| 247 | + --output-level|-l) |
| 248 | + shift |
| 249 | + value="${1:?No value for --output-level}" |
| 250 | + shift |
| 251 | + VERBOSITY=$((value + 0)) |
| 252 | + ;; |
| 253 | + #: --verbose, -v |
| 254 | + #: Increase output verbosity. |
| 255 | + --verbose|-v) |
| 256 | + shift |
| 257 | + VERBOSITY=$((VERBOSITY + 1)) |
| 258 | + ;; |
| 259 | + #: --quiet, -q |
| 260 | + #: Decrease output verbosity. |
| 261 | + --quiet|-q) |
| 262 | + shift |
| 263 | + VERBOSITY=$((VERBOSITY - 1)) |
| 264 | + ;; |
| 265 | + #: --personal-mimedir BOOL (default: 0) |
| 266 | + #: Load mimetypes etc. from ~/.local/share/mime too. |
| 267 | + --personal-mimedir) |
| 268 | + shift |
| 269 | + flag="${1:?No value for --personal-mimedir}" |
| 270 | + shift |
| 271 | + SCAN_PERSONAL_MIMEDIR=$(boolify "$flag") |
| 272 | + ;; |
| 273 | + -*) |
| 274 | + printf 'Unrecognised option: "%s"\n' "$1" |
| 275 | + shift |
| 276 | + return 1 |
| 277 | + ;; |
| 278 | + *) |
| 279 | + break |
| 280 | + ;; |
| 281 | + esac |
| 282 | + done |
| 283 | + |
| 284 | + #: |
| 285 | + #: By default, at output level 1, this script shows which MIMEtypes |
| 286 | + #: are falling back to "<type>-x-generic.svg". That's usually the best |
| 287 | + #: place to look for mimetypes which need something more specific. |
| 288 | + #: |
| 289 | + |
| 290 | + if [ $# -ne 0 ]; then |
| 291 | + printf "%s\n" "This script takes no positional parameters" |
| 292 | + printf "\n" |
| 293 | + return 1 |
| 294 | + fi |
| 295 | + |
| 296 | + return 0 |
| 297 | +} |
| 298 | + |
| 299 | + |
| 300 | +# Main script flow. |
| 301 | + |
| 302 | +main () { |
| 303 | + if ! parse_args "$@"; then |
| 304 | + printf >&2 "ERROR: failed to parse command line\n" |
| 305 | + show_usage |
| 306 | + exit 1 |
| 307 | + fi |
| 308 | + local tempdir |
| 309 | + tempdir=$(mktemp --directory) |
| 310 | + ( cd "$tempdir" && find_missing_mimetype_icons ) || true |
| 311 | + rm -fr "$tempdir" |
| 312 | +} |
| 313 | +main "$@" |
0 commit comments