Skip to content

Commit 09488fa

Browse files
committed
[mod] fc_install: font config installer for the nerd-fonts
1 parent d46b97a commit 09488fa

File tree

3 files changed

+379
-0
lines changed

3 files changed

+379
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
*~
12
temp-glyph-source-fonts/*
23
temp/*
34
patched-fonts/Input*

fc_install

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
#!/usr/bin/env bash
2+
# -*- mode: sh; sh-shell: bash -*-
3+
4+
# SPDX-License-Identifier: MIT
5+
# Author: Markus Heiser <[email protected]>
6+
# Keywords: NerdFonts
7+
8+
### Commentary:
9+
#
10+
# Fontconfig [1] installer to install or update the NerdFonts [1] from the
11+
# GitHub releases [2].
12+
#
13+
# Usage:
14+
#
15+
# $ curl -s https://raw.githubusercontent.com/ryanoasis/nerd-fonts/master/fc_install -o fc_install
16+
# $ chmod ugo+x fc_install
17+
# $ ./fc_install --help
18+
#
19+
# Developer notes:
20+
#
21+
# $ shfmt -i 4 -w fc_install
22+
# $ shellcheck fc_install
23+
#
24+
# [1] https://www.freedesktop.org/wiki/Software/fontconfig/
25+
# [2] https://www.nerdfonts.com/
26+
# [3] https://github.com/$GH_OWNER/$GH_REPO/releases
27+
28+
### Code:
29+
30+
set -euo pipefail
31+
shopt -s inherit_errexit
32+
33+
# environment
34+
# -----------
35+
36+
VERBOSE="${VERBOSE:-1}"
37+
# https://docs.github.com/de/rest/releases/releases?#get-a-release-by-tag-name
38+
GH_API_VERSION="${GH_API_VERSION:-2022-11-28}"
39+
GH_RELEASE_TAG="${GH_RELEASE_TAG:-latest}"
40+
GH_OWNER="${GH_OWNER:-ryanoasis}"
41+
GH_REPO="${GH_REPO:-nerd-fonts}"
42+
FONT_FORMATS=(ttf otf)
43+
if [ ${EUID:-0} -ne 0 ] || [ "$(id -u)" -ne 0 ]; then
44+
FONT_DIR="${FONT_DIR:-${HOME}/.local/share/fonts/NerdFonts}"
45+
else
46+
FONT_DIR="${FONT_DIR:-/usr/local/share/fonts/NerdFonts}"
47+
fi
48+
49+
_REQUIREMENTS=("curl" "fc-cache")
50+
_GH_RELEASE_DATA=""
51+
52+
# command line interface
53+
# ----------------------
54+
55+
cmd.help() {
56+
cat <<EOF
57+
Usage: $0 <cmd>
58+
59+
Install and update Nerd Fonts [1] from the GitHub releases [2].
60+
61+
[1] https://www.nerdfonts.com/
62+
[2] https://github.com/$GH_OWNER/$GH_REPO/releases
63+
64+
cmd:
65+
help : show this help message
66+
env : show environment
67+
list : list released fonts
68+
install : selectively install (or update) a font or *all* fonts
69+
remove : uninstall all Nerd Fonts
70+
71+
required tools:
72+
${_REQUIREMENTS[*]}
73+
EOF
74+
}
75+
76+
cmd.install.help() {
77+
cat <<EOF
78+
Selectively install one font or *all* fonts to FONT_DIR.
79+
80+
Usage: $0 install [<fontname>|all]
81+
82+
fontname:
83+
The name of the font to be installed can be specified, or 'all' can be
84+
specified to install all fonts. If no specification is made, a list of
85+
available fonts will be displayed, and a font must be selected from the list.
86+
EOF
87+
}
88+
cmd.install() {
89+
local font_name="${1-}"
90+
local font_list=()
91+
local tmp_folder
92+
93+
gh.release_data >/dev/null
94+
IFS=$'\n' read -r -d '' -a font_list < <(nerd.font_list && printf '\0')
95+
96+
if [ "$font_name" == "all" ]; then
97+
msg.info "install all ${#font_list[@]} fonts"
98+
elif [ "$font_name" == "" ]; then
99+
PS3="Enter a number: "
100+
select font_name in "${font_list[@]}" "all"; do
101+
if [ "$font_name" = "all" ]; then
102+
msg.warn "installing all fonts will take its time / time for a coffee break"
103+
break
104+
elif sh.in_array "$font_name" "${font_list[@]}"; then
105+
font_list=("$font_name")
106+
break
107+
else
108+
msg.err "invalid choice."
109+
fi
110+
done
111+
msg.debug "user selected font $font_name"
112+
else
113+
sh.in_array "$font_name" "${font_list[@]}" ||
114+
sh.die.err 42 "font $font_name does not exists in release $GH_RELEASE_TAG"
115+
fi
116+
117+
msg.info "install fonts into folder: $FONT_DIR"
118+
tmp_folder="$(mktemp -d)"
119+
msg.debug "cd $tmp_folder"
120+
pushd "$tmp_folder" &>/dev/null || sh.die.err 42 "can't cd $tmp_folder"
121+
for font in "${font_list[@]}"; do
122+
nerd.install_font "$font"
123+
done
124+
popd &>/dev/null
125+
rm -rf "$tmp_folder"
126+
msg.info "fontconfig: build font information cache files"
127+
fc-cache
128+
}
129+
130+
cmd.remove.help() {
131+
cat <<EOF
132+
Usage: $0 remove
133+
134+
Uninstall all previous installed Nerd Fonts.
135+
EOF
136+
}
137+
138+
cmd.remove() {
139+
[ "$#" -ne 0 ] && sh.die.err 42 "${FUNCNAME#"cmd."}: unknown arguments $*"
140+
if [ -d "$FONT_DIR" ]; then
141+
msg.info "remove font folder $FONT_DIR"
142+
rm -rf "$FONT_DIR"
143+
else
144+
msg.err "Nerd Fonts not installed at $FONT_DIR"
145+
fi
146+
}
147+
148+
cmd.list() {
149+
[ "$#" -ne 0 ] && sh.die.err 42 "${FUNCNAME#"cmd."}: unknown arguments $*"
150+
if [ "$GH_RELEASE_TAG" = "latest" ]; then
151+
GH_RELEASE_TAG="$(gh.latest_release)"
152+
msg.info "$GH_OWNER/$GH_REPO: latest ($GH_RELEASE_TAG)"
153+
else
154+
msg.info "$GH_OWNER/$GH_REPO: $GH_RELEASE_TAG"
155+
fi
156+
nerd.font_list
157+
}
158+
159+
cmd.env() {
160+
[ "$#" -ne 0 ] && sh.die.err 42 "${FUNCNAME#"cmd."}: unknown arguments $*"
161+
cat <<EOF
162+
GH_API_VERSION="${GH_API_VERSION}"
163+
GH_RELEASE_TAG="${GH_RELEASE_TAG}"
164+
GH_OWNER="${GH_OWNER}"
165+
GH_REPO="${GH_REPO}"
166+
FONT_DIR="${FONT_DIR}"
167+
FONT_FORMATS=(${FONT_FORMATS[@]})
168+
VERBOSE="${VERBOSE}"
169+
EOF
170+
}
171+
172+
# Nerd Fonts
173+
# ----------
174+
175+
nerd.released_zip() {
176+
gh.release_data |
177+
grep "browser_download_url.*zip" |
178+
cut -d : -f 2,3 |
179+
tr -d \"
180+
}
181+
182+
nerd.font_list() {
183+
while IFS= read -r line; do
184+
echo "$line" | sed 's/^.*\/\(.*\).zip$/\1/'
185+
done < <(nerd.released_zip)
186+
}
187+
188+
nerd.install_font() {
189+
# usage: nerd.install_font <font name>
190+
191+
msg.info "download & install font: ${1}"
192+
(
193+
set -e
194+
local dst
195+
gh.download_asset "${1}.zip"
196+
mkdir -p "${1}"
197+
unzip -q -o -d "${1}" "${1}.zip"
198+
mkdir -p "$FONT_DIR"
199+
for filename in "${1}"/*; do
200+
if sh.in_array "${filename##*.}" "${FONT_FORMATS[@]}"; then
201+
dst="$FONT_DIR/$(basename "$filename")"
202+
msg.debug "install font: $dst"
203+
mv "$filename" "$dst"
204+
fi
205+
done
206+
)
207+
sh.prompt-err $?
208+
}
209+
210+
# github tools
211+
# ------------
212+
213+
gh.latest_release() {
214+
msg.debug "gh.latest_release() - URL https://github.com/$GH_OWNER/$GH_REPO/releases/latest"
215+
basename "$(curl -fs -o/dev/null -w "%{redirect_url}" "https://github.com/$GH_OWNER/$GH_REPO/releases/latest")"
216+
217+
}
218+
219+
gh.release_data() {
220+
gh.release_tag
221+
if [ "$_GH_RELEASE_DATA" = "" ]; then
222+
_GH_RELEASE_DATA="$(gh.get_release_data)"
223+
fi
224+
if echo "$_GH_RELEASE_DATA" | grep '"message": "Not Found"' &>/dev/null; then
225+
msg.debug "release data: $_GH_RELEASE_DATA"
226+
sh.die.err 42 "release tag $GH_RELEASE_TAG does not exists"
227+
fi
228+
echo "$_GH_RELEASE_DATA"
229+
}
230+
231+
gh.release_tag() {
232+
if [ "$GH_RELEASE_TAG" = "latest" ]; then
233+
GH_RELEASE_TAG="$(gh.latest_release)"
234+
fi
235+
echo "$GH_RELEASE_TAG"
236+
}
237+
238+
gh.get_release_data() {
239+
local url
240+
url="https://api.github.com/repos/$GH_OWNER/$GH_REPO/releases/tags/$(gh.release_tag)"
241+
msg.debug "gh.get_release_data() URL $url"
242+
curl --silent -L \
243+
-H "Accept: application/vnd.github+json" \
244+
-H "X-GitHub-Api-Version: $GH_API_VERSION" \
245+
"$url"
246+
}
247+
248+
gh.download_asset() {
249+
# usage: gh.download_asset <asset filename>
250+
251+
local fname="${1}"
252+
local url
253+
local filesize
254+
255+
url="https://github.com/$GH_OWNER/$GH_REPO/releases/download/$(gh.release_tag)/${fname}"
256+
msg.debug "gh.download_asset URL $url"
257+
curl -L -# "${url}" -o "${fname}" || sh.die.err $? "can't download $url"
258+
259+
# check if the response from GH is just a "Not Found"
260+
filesize=$(stat -c%s "${fname}")
261+
if [ 15 -ge "$filesize" ]; then
262+
if [ "$(head -c 10 "${fname}")" = "Not Found" ]; then
263+
msg.err "Asset Not Found: $url"
264+
return 42
265+
fi
266+
fi
267+
}
268+
269+
# script helpers
270+
# --------------
271+
272+
if [ ! -p /dev/stdout ] && [ ! "${TERM}" = 'dumb' ] && [ ! "${TERM}" = 'unknown' ]; then
273+
_BYellow='\e[1;33m'
274+
_BBlue='\e[1;94m'
275+
_BRed='\e[1;31m'
276+
# SGR (Select Graphic Rendition) parameters
277+
_creset='\e[0m' # reset all attributes
278+
else
279+
_BYellow=''
280+
_BBlue=''
281+
_BRed=''
282+
# SGR (Select Graphic Rendition) parameters
283+
_creset='' # reset all attributes
284+
fi
285+
286+
msg.err() {
287+
echo -e "${_BRed}ERROR:${_creset} $*" >&2
288+
return 0
289+
}
290+
msg.warn() {
291+
echo -e "${_BBlue}WARN:${_creset} $*" >&2
292+
return 0
293+
}
294+
msg.info() {
295+
if [ "${VERBOSE}" -ge 1 ]; then
296+
echo -e "${_BYellow}INFO:${_creset} $*" >&2
297+
fi
298+
return 0
299+
}
300+
msg.debug() {
301+
if [ "${VERBOSE}" -ge 2 ]; then
302+
echo -e "${_BYellow}DEBUG:${_creset} $*" >&2
303+
fi
304+
return 0
305+
}
306+
307+
sh.die.err() {
308+
msg.err "(${1-1}) ${2-died} "
309+
exit "${1-1}"
310+
}
311+
312+
sh.prompt-err() {
313+
314+
## Use this as last command in your function to prompt an ERROR message if
315+
## the exit code is not zero.
316+
317+
local err=${1}
318+
[ "$err" -ne "0" ] && msg.err "${FUNCNAME[1]} exit with error ($err)"
319+
return "$err"
320+
}
321+
322+
sh.in_array() {
323+
local word="${1}"
324+
shift
325+
for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
326+
return 1
327+
}
328+
329+
scripts.requires() {
330+
local exit_val=0
331+
while [ -n "${1-}" ]; do
332+
if ! command -v "${1}" &>/dev/null; then
333+
msg.err "missing command ${1}"
334+
exit_val=42
335+
fi
336+
shift
337+
done
338+
return $exit_val
339+
}
340+
341+
main() {
342+
local cmd="${1:-install}"
343+
shift || true
344+
scripts.requires "${_REQUIREMENTS[@]}" || sh.die.err $? "first install missing requirements"
345+
346+
case "$cmd" in
347+
help | --help) cmd.help ;;
348+
*)
349+
_type="$(type -t "cmd.$cmd")" || true
350+
if [ "$_type" != "function" ]; then
351+
sh.die.err 42 "unknown command: $cmd / use --help"
352+
fi
353+
354+
if [ "${1-}" == '--help' ]; then
355+
_type="$(type -t "cmd.$cmd.help")" || true
356+
if [ "$_type" = 'function' ]; then
357+
"cmd.${cmd}.help"
358+
else
359+
"cmd.help"
360+
fi
361+
else
362+
[ "${VERBOSE}" -ge 3 ] && set -x
363+
"cmd.$cmd" "$@"
364+
fi
365+
;;
366+
esac
367+
}
368+
369+
### fc_install ends here ..
370+
main "$@"

readme.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ The following flow diagram shows the current glyph sets included:
7575

7676
### Various Download Options for Fonts
7777

78+
On Linux, to install fonts from [(latest) release](https://github.com/ryanoasis/nerd-fonts/releases/latest) use the [font config](https://www.freedesktop.org/wiki/Software/fontconfig/) installer:
79+
80+
```bash
81+
curl -s https://raw.githubusercontent.com/ryanoasis/nerd-fonts/master/fc_install -o fc_install
82+
chmod ugo+x fc_install
83+
./fc_install --help
84+
```
85+
7886
_If you..._
7987

8088
* `Option 1.` want to download a **font family** package of variations (bold, italic, etc.) see [download an archive](#option-1-release-archive-download)

0 commit comments

Comments
 (0)