at master 313 lines 12 kB view raw
1# shellcheck shell=bash 2# Setup hook for the `installShellFiles` package. 3# 4# Example usage in a derivation: 5# 6# { …, installShellFiles, … }: 7# stdenv.mkDerivation { 8# … 9# nativeBuildInputs = [ installShellFiles ]; 10# postInstall = '' 11# installManPage share/doc/foobar.1 12# installShellCompletion share/completions/foobar.{bash,fish,zsh} 13# ''; 14# … 15# } 16# 17# See comments on each function for more details. 18 19# installManPage [--name <path>] <path> [...<path>] 20# 21# Each argument is checked for its man section suffix and installed into the appropriate 22# share/man/man<n>/ directory. The function returns an error if any paths don't have the man 23# section suffix (with optional .gz compression). 24# 25# Optionally accepts pipes as input, which when provided require the `--name` argument to 26# name the output file. 27# 28# installManPage --name foobar.1 <($out/bin/foobar --manpage) 29installManPage() { 30 local arg name='' continueParsing=1 31 while { arg=$1; shift; }; do 32 if (( continueParsing )); then 33 case "$arg" in 34 --name) 35 name=$1 36 shift || { 37 nixErrorLog "${FUNCNAME[0]}: --name flag expected an argument" 38 return 1 39 } 40 continue;; 41 --name=*) 42 # Treat `--name=foo` that same as `--name foo` 43 name=${arg#--name=} 44 continue;; 45 --) 46 continueParsing=0 47 continue;; 48 esac 49 fi 50 51 nixInfoLog "${FUNCNAME[0]}: installing $arg${name:+ as $name}" 52 local basename 53 54 # Check if path is empty 55 if test -z "$arg"; then 56 # It is an empty string 57 nixErrorLog "${FUNCNAME[0]}: path cannot be empty" 58 return 1 59 fi 60 61 if test -n "$name"; then 62 # Provided name. Required for pipes, optional for paths 63 basename=$name 64 elif test -p "$arg"; then 65 # Named pipe requires a file name 66 nixErrorLog "${FUNCNAME[0]}: named pipe requires --name argument" 67 else 68 # Normal file without a name 69 basename=$(stripHash "$arg") # use stripHash in case it's a nix store path 70 fi 71 72 # Check that it is well-formed 73 local trimmed=${basename%.gz} # don't get fooled by compressed manpages 74 local suffix=${trimmed##*.} 75 if test -z "$suffix" -o "$suffix" = "$trimmed"; then 76 nixErrorLog "${FUNCNAME[0]}: path missing manpage section suffix: $arg" 77 return 1 78 fi 79 80 # Create the out-path 81 local outRoot 82 if test "$suffix" = 3; then 83 outRoot=${!outputDevman:?} 84 else 85 outRoot=${!outputMan:?} 86 fi 87 local outPath="${outRoot}/share/man/man$suffix/" 88 nixInfoLog "${FUNCNAME[0]}: installing to $outPath" 89 90 # Install 91 if test -p "$arg"; then 92 # install doesn't work with pipes on Darwin 93 mkdir -p "$outPath" && cat "$arg" > "$outPath/$basename" 94 else 95 install -D --mode=644 --no-target-directory -- "$arg" "$outPath/$basename" 96 fi 97 98 # Reset the name for the next page 99 name= 100 done 101} 102 103# installShellCompletion [--cmd <name>] ([--bash|--fish|--zsh] [--name <name>] <path>)... 104# 105# Each path is installed into the appropriate directory for shell completions for the given shell. 106# If one of `--bash`, `--fish`, `--zsh`, or `--nushell` is given the path is assumed to belong to 107# that shell. Otherwise the file extension will be examined to pick a shell. If the shell is 108# unknown a warning will be logged and the command will return a non-zero status code after 109# processing any remaining paths. Any of the shell flags will affect all subsequent paths (unless 110# another shell flag is given). 111# 112# If the shell completion needs to be renamed before installing the optional `--name <name>` flag 113# may be given. Any name provided with this flag only applies to the next path. 114# 115# If all shell completions need to be renamed before installing the optional `--cmd <name>` flag 116# may be given. This will synthesize a name for each file, unless overridden with an explicit 117# `--name` flag. For example, `--cmd foobar` will synthesize the name `_foobar` for zsh and 118# `foobar.bash` for bash. 119# 120# For zsh completions, if the `--name` flag is not given, the path will be automatically renamed 121# such that `foobar.zsh` becomes `_foobar`. 122# 123# A path may be a named fd, such as produced by the bash construct `<(cmd)`. When using a named fd, 124# the shell type flag must be provided, and either the `--name` or `--cmd` flag must be provided. 125# This might look something like: 126# 127# installShellCompletion --zsh --name _foobar <($out/bin/foobar --zsh-completion) 128# 129# This command accepts multiple shell flags in conjunction with multiple paths if you wish to 130# install them all in one command: 131# 132# installShellCompletion share/completions/foobar.{bash,fish} --zsh share/completions/_foobar 133# 134# However it may be easier to read if each shell is split into its own invocation, especially when 135# renaming is involved: 136# 137# installShellCompletion --bash --name foobar.bash share/completions.bash 138# installShellCompletion --fish --name foobar.fish share/completions.fish 139# installShellCompletion --nushell --name foobar share/completions.nu 140# installShellCompletion --zsh --name _foobar share/completions.zsh 141# 142# Or to use shell newline escaping to split a single invocation across multiple lines: 143# 144# installShellCompletion --cmd foobar \ 145# --bash <($out/bin/foobar --bash-completion) \ 146# --fish <($out/bin/foobar --fish-completion) \ 147# --nushell <($out/bin/foobar --nushell-completion) 148# --zsh <($out/bin/foobar --zsh-completion) 149# 150# If any argument is `--` the remaining arguments will be treated as paths. 151installShellCompletion() { 152 local shell='' name='' cmdname='' retval=0 parseArgs=1 arg 153 while { arg=$1; shift; }; do 154 # Parse arguments 155 if (( parseArgs )); then 156 case "$arg" in 157 --bash|--fish|--zsh|--nushell) 158 shell=${arg#--} 159 continue;; 160 --name) 161 name=$1 162 shift || { 163 nixErrorLog "${FUNCNAME[0]}: --name flag expected an argument" 164 return 1 165 } 166 continue;; 167 --name=*) 168 # treat `--name=foo` the same as `--name foo` 169 name=${arg#--name=} 170 continue;; 171 --cmd) 172 cmdname=$1 173 shift || { 174 nixErrorLog "${FUNCNAME[0]}: --cmd flag expected an argument" 175 return 1 176 } 177 continue;; 178 --cmd=*) 179 # treat `--cmd=foo` the same as `--cmd foo` 180 cmdname=${arg#--cmd=} 181 continue;; 182 --?*) 183 nixWarnLog "${FUNCNAME[0]}: unknown flag ${arg%%=*}" 184 retval=2 185 continue;; 186 --) 187 # treat remaining args as paths 188 parseArgs=0 189 continue;; 190 esac 191 fi 192 nixInfoLog "${FUNCNAME[0]}: installing $arg${name:+ as $name}" 193 # if we get here, this is a path or named pipe 194 # Identify shell and output name 195 local curShell=$shell 196 local outName='' 197 if [[ -z "$arg" ]]; then 198 nixErrorLog "${FUNCNAME[0]}: empty path is not allowed" 199 return 1 200 elif [[ -p "$arg" ]]; then 201 # this is a named fd or fifo 202 if [[ -z "$curShell" ]]; then 203 nixErrorLog "${FUNCNAME[0]}: named pipe requires one of --bash, --fish, --zsh, or --nushell" 204 return 1 205 elif [[ -z "$name" && -z "$cmdname" ]]; then 206 nixErrorLog "${FUNCNAME[0]}: named pipe requires one of --cmd or --name" 207 return 1 208 fi 209 else 210 # this is a path 211 local argbase 212 argbase=$(stripHash "$arg") 213 if [[ -z "$curShell" ]]; then 214 # auto-detect the shell 215 case "$argbase" in 216 ?*.bash) curShell=bash;; 217 ?*.fish) curShell=fish;; 218 ?*.nu) curShell=nushell;; 219 ?*.zsh) curShell=zsh;; 220 *) 221 if [[ "$argbase" = _* && "$argbase" != *.* ]]; then 222 # probably zsh 223 nixWarnLog "${FUNCNAME[0]}: assuming path \`$arg' is zsh; please specify with --zsh" 224 curShell=zsh 225 else 226 nixWarnLog "${FUNCNAME[0]}: unknown shell for path: $arg" >&2 227 retval=2 228 continue 229 fi;; 230 esac 231 fi 232 outName=$argbase 233 fi 234 # Identify output path 235 if [[ -n "$name" ]]; then 236 outName=$name 237 elif [[ -n "$cmdname" ]]; then 238 case "$curShell" in 239 bash|fish) outName=$cmdname.$curShell;; 240 nushell) outName=$cmdname.nu;; 241 zsh) outName=_$cmdname;; 242 *) 243 # Our list of shells is out of sync with the flags we accept or extensions we detect. 244 nixErrorLog "${FUNCNAME[0]}: internal: shell $curShell not recognized" 245 return 1;; 246 esac 247 fi 248 local sharePath 249 case "$curShell" in 250 bash) sharePath=bash-completion/completions;; 251 fish) sharePath=fish/vendor_completions.d;; 252 nushell) sharePath=nushell/vendor/autoload;; 253 zsh) 254 sharePath=zsh/site-functions 255 # only apply automatic renaming if we didn't have a manual rename 256 if [[ -z "$name" && -z "$cmdname" ]]; then 257 # convert a name like `foo.zsh` into `_foo` 258 outName=${outName%.zsh} 259 outName=_${outName#_} 260 fi;; 261 *) 262 # Our list of shells is out of sync with the flags we accept or extensions we detect. 263 nixErrorLog "${FUNCNAME[0]}: internal: shell $curShell not recognized" 264 return 1;; 265 esac 266 # Install file 267 local outDir="${!outputBin:?}/share/$sharePath" 268 local outPath="$outDir/$outName" 269 if [[ -p "$arg" ]]; then 270 # install handles named pipes on NixOS but not on macOS 271 mkdir -p "$outDir" \ 272 && cat "$arg" > "$outPath" 273 else 274 install -D --mode=644 --no-target-directory "$arg" "$outPath" 275 fi 276 277 if [ ! -s "$outPath" ]; then 278 nixErrorLog "${FUNCNAME[0]}: installed shell completion file \`$outPath' does not exist or has zero size" 279 return 1 280 fi 281 # Clear the per-path flags 282 name= 283 done 284 if [[ -n "$name" ]]; then 285 nixErrorLog "${FUNCNAME[0]}: --name flag given with no path" >&2 286 return 1 287 fi 288 return $retval 289} 290 291# installBin <path> [...<path>] 292# 293# Install each argument to $outputBin 294installBin() { 295 local path 296 for path in "$@"; do 297 if test -z "$path"; then 298 nixErrorLog "${FUNCNAME[0]}: path cannot be empty" 299 return 1 300 fi 301 nixInfoLog "${FUNCNAME[0]}: installing $path" 302 303 local basename 304 # use stripHash in case it's a nix store path 305 basename=$(stripHash "$path") 306 307 local outRoot 308 outRoot=${!outputBin:?} 309 310 local outPath="${outRoot}/bin/$basename" 311 install -D --mode=755 --no-target-directory "$path" "${outRoot}/bin/$basename" 312 done 313}