mirror of
https://github.com/jorgebucaran/fisher
synced 2024-10-09 09:36:53 +02:00
2a6c36b8a9
Bug was introduced in 7850f78e8a
.
When removing a file don't use `functions -q $filename` as a loose
check for a .fish file. Use a string match instead.
`functions -q $filename` will cause the function defined in the file
to be autoloaded (if it wasn't already) which can cause trouble with
some "dubious" packages that run arbitrary code in function files.
428 lines
14 KiB
Fish
428 lines
14 KiB
Fish
set -g fisher_version 3.0.5
|
|
|
|
type source >/dev/null; or function source; . $argv; end
|
|
|
|
switch (command uname)
|
|
case Darwin FreeBSD
|
|
function _fisher_now -a elapsed
|
|
command perl -MTime::HiRes -e 'printf("%.0f\n", (Time::HiRes::time() * 1000) - $ARGV[0])' $elapsed
|
|
end
|
|
case \*
|
|
function _fisher_now -a elapsed
|
|
command date "+%s%3N" | command awk "{ sub(/3N\$/,\"500\"); print \$0 - 0$elapsed }"
|
|
end
|
|
end
|
|
|
|
function fisher -a cmd -d "fish package manager"
|
|
if not command which curl >/dev/null
|
|
echo "curl is required to use fisher -- install curl and try again" >&2
|
|
return 1
|
|
end
|
|
|
|
test -z "$XDG_CACHE_HOME"; and set XDG_CACHE_HOME ~/.cache
|
|
test -z "$XDG_CONFIG_HOME"; and set XDG_CONFIG_HOME ~/.config
|
|
|
|
set -g fish_config $XDG_CONFIG_HOME/fish
|
|
set -g fisher_cache $XDG_CACHE_HOME/fisher
|
|
set -g fisher_config $XDG_CONFIG_HOME/fisher
|
|
|
|
test -z "$fisher_path"; and set -g fisher_path $fish_config
|
|
|
|
command mkdir -p {$fish_config,$fisher_path}/{functions,completions,conf.d} $fisher_cache
|
|
|
|
if test ! -e "$fisher_path/completions/fisher.fish"
|
|
echo "fisher self-complete" > $fisher_path/completions/fisher.fish
|
|
_fisher_self_complete
|
|
end
|
|
|
|
switch "$cmd"
|
|
case self-complete
|
|
_fisher_self_complete
|
|
case ls
|
|
_fisher_ls | command sed "s|$HOME|~|"
|
|
case -v {,--}version
|
|
_fisher_version (status -f)
|
|
case -h {,--}help
|
|
_fisher_help
|
|
case self-update
|
|
_fisher_self_update (status -f); or return
|
|
_fisher_self_complete
|
|
case self-uninstall
|
|
_fisher_self_uninstall
|
|
case add rm ""
|
|
if test ! -z "$argv"
|
|
if not isatty
|
|
while read -l i
|
|
set argv $argv $i
|
|
end
|
|
end
|
|
end
|
|
_fisher_commit $argv; or return
|
|
_fisher_self_complete
|
|
case \*
|
|
echo "error: unknown flag or command \"$cmd\"" >&2
|
|
_fisher_help >&2
|
|
return 1
|
|
end
|
|
end
|
|
|
|
function _fisher_self_complete
|
|
complete -c fisher --erase
|
|
complete -xc fisher -n __fish_use_subcommand -a version -d "Show version"
|
|
complete -xc fisher -n __fish_use_subcommand -a help -d "Show help"
|
|
complete -xc fisher -n __fish_use_subcommand -a self-update -d "Update fisher"
|
|
complete -xc fisher -n __fish_use_subcommand -a ls -d "List installed packages"
|
|
complete -xc fisher -n __fish_use_subcommand -a rm -d "Remove packages"
|
|
complete -xc fisher -n __fish_use_subcommand -a add -d "Add packages"
|
|
for pkg in (_fisher_ls)
|
|
complete -xc fisher -n "__fish_seen_subcommand_from rm" -a $pkg
|
|
end
|
|
end
|
|
|
|
function _fisher_ls
|
|
set -l pkgs $fisher_config/*/*/*
|
|
for pkg in $pkgs
|
|
command readlink $pkg; and continue; or echo $pkg
|
|
end | command sed "s|$fisher_config/||;s|github\.com/||"
|
|
end
|
|
|
|
function _fisher_version -a file
|
|
echo "fisher version $fisher_version $file" | command sed "s|$HOME|~|"
|
|
end
|
|
|
|
function _fisher_help
|
|
echo "usage: fisher add <PACKAGES> add packages"
|
|
echo " fisher rm <PACKAGES> remove packages"
|
|
echo " fisher ls list installed packages"
|
|
echo " fisher self-update update fisher"
|
|
echo " fisher self-uninstall uninstall fisher & all packages"
|
|
echo " fisher help show this help"
|
|
echo " fisher version show version"
|
|
echo
|
|
echo "examples:"
|
|
echo " fisher add jethrokuan/z rafaelrinaldi/pure"
|
|
echo " fisher add gitlab.com/owner/foobar@v2"
|
|
echo " fisher add ~/myfish/mypkg"
|
|
echo " fisher rm rafaelrinaldi/pure"
|
|
echo " fisher ls | fisher rm"
|
|
end
|
|
|
|
function _fisher_self_update -a file
|
|
set -l url "https://raw.githubusercontent.com/jorgebucaran/fisher/master/fisher.fish"
|
|
echo "fetching $url" >&2
|
|
|
|
curl -s "$url?nocache" >$file@
|
|
|
|
set -l next_version (awk 'NR == 1 { print $4; exit }' < $file@)
|
|
switch "$next_version"
|
|
case "" $fisher_version
|
|
command rm -f $file@
|
|
if test -z "$next_version"
|
|
echo "cannot update fisher -- are you offline?" >&2
|
|
return 1
|
|
end
|
|
echo "fisher is already up-to-date" >&2
|
|
case \*
|
|
echo "linking $file" | command sed "s|$HOME|~|" >&2
|
|
command mv -f $file@ $file
|
|
source $file
|
|
echo "updated fisher to $fisher_version -- hooray!" >&2
|
|
end
|
|
end
|
|
|
|
function _fisher_self_uninstall
|
|
set -l current_pkgs $fisher_config/*/*/*
|
|
for path in $fisher_cache (_fisher_pkg_remove_all $current_pkgs) $fisher_config $fisher_path/{functions,completions}/fisher.fish $fish_config/fishfile
|
|
echo "removing $path"
|
|
command rm -rf $path 2>/dev/null
|
|
end | command sed "s|$HOME|~|" >&2
|
|
|
|
set -e fisher_cache
|
|
set -e fisher_config
|
|
set -e fisher_path
|
|
set -e fisher_version
|
|
|
|
complete -c fisher --erase
|
|
functions -e (functions -a | command awk '/^_fisher/') fisher
|
|
|
|
echo "done -- see you again!" >&2
|
|
end
|
|
|
|
function _fisher_commit
|
|
set -l elapsed (_fisher_now)
|
|
set -l current_pkgs $fisher_config/*/*/*
|
|
set -l removed_pkgs (_fisher_pkg_remove_all $current_pkgs)
|
|
command rm -rf $fisher_config
|
|
command mkdir -p $fisher_config
|
|
|
|
set -l fishfile $fish_config/fishfile
|
|
if test ! -e "$fishfile"
|
|
command touch $fishfile
|
|
echo "created empty fishfile in $fishfile" | command sed "s|$HOME|~|" >&2
|
|
end
|
|
_fisher_fishfile_indent (echo -s $argv\;) < $fishfile > $fishfile@
|
|
command mv -f $fishfile@ $fishfile
|
|
command rm -f $fishfile@
|
|
|
|
set -l expected_pkgs (_fisher_fishfile_load < $fishfile)
|
|
set -l added_pkgs (_fisher_pkg_fetch_all $expected_pkgs)
|
|
set -l updated_pkgs (
|
|
for pkg in $removed_pkgs
|
|
set pkg (echo $pkg | command sed "s|$fisher_config/||")
|
|
if contains -- $pkg $added_pkgs
|
|
echo $pkg
|
|
end
|
|
end)
|
|
|
|
if test -z "$added_pkgs$updated_pkgs$removed_pkgs$expected_pkgs"
|
|
echo "nothing to commit -- try adding some packages" >&2
|
|
return 1
|
|
end
|
|
|
|
echo (count $added_pkgs) (count $updated_pkgs) (count $removed_pkgs) (_fisher_now $elapsed) | _fisher_status_report >&2
|
|
end
|
|
|
|
function _fisher_pkg_remove_all
|
|
for pkg in $argv
|
|
echo $pkg
|
|
_fisher_pkg_uninstall $pkg
|
|
end
|
|
end
|
|
|
|
function _fisher_pkg_fetch_all
|
|
set -l pkg_jobs
|
|
set -l local_pkgs
|
|
set -l actual_pkgs
|
|
set -l expected_pkgs
|
|
|
|
for name in $argv
|
|
switch $name
|
|
case \~\* /\*
|
|
set -l path (echo "$name" | command sed "s|~|$HOME|")
|
|
if test -e "$path"
|
|
set local_pkgs $local_pkgs $path
|
|
else
|
|
echo "cannot install \"$name\" -- is this a valid file?" >&2
|
|
end
|
|
continue
|
|
case https://\* ssh://\* {github,gitlab}.com/\* bitbucket.org/\*
|
|
case \*/\*
|
|
set name "github.com/$name"
|
|
case \*
|
|
echo "cannot install \"$name\" without a prefix -- should be <owner>/$name" >&2
|
|
continue
|
|
end
|
|
|
|
echo $name | command awk '{
|
|
split($0, tmp, /@/)
|
|
|
|
pkg = tmp[1]
|
|
tag = tmp[2] ? tmp[2] : "master"
|
|
name = tmp[split(pkg, tmp, "/")]
|
|
|
|
print (\
|
|
pkg ~ /^github\.com/ ? "https://codeload."pkg"/tar.gz/"tag : \
|
|
pkg ~ /^gitlab\.com/ ? "https://"pkg"/-/archive/"tag"/"name"-"tag".tar.gz" : \
|
|
pkg ~ /^bitbucket\.org/ ? "https://"pkg"/get/"tag".tar.gz" : pkg \
|
|
) "\t" pkg
|
|
}' | read -l url pkg
|
|
|
|
if test ! -d "$fisher_config/$pkg"
|
|
fish -c "
|
|
echo fetching $url >&2
|
|
command mkdir -p \"$fisher_config/$pkg\"
|
|
|
|
if curl -Ss $url 2>&1 | tar -xzf- -C \"$fisher_config/$pkg\" --strip-components=1 2>/dev/null
|
|
command mkdir -p \"$fisher_cache/$pkg\"
|
|
command cp -Rf \"$fisher_config/$pkg\" \"$fisher_cache/$pkg/..\"
|
|
else if test -d \"$fisher_cache/$pkg\"
|
|
echo cannot connect to server -- using data from \"$fisher_cache/$pkg\" | command sed 's|$HOME|~|' >&2
|
|
command cp -Rf \"$fisher_cache/$pkg\" \"$fisher_config/$pkg/..\"
|
|
else
|
|
command rm -rf \"$fisher_config/$pkg\"
|
|
echo cannot install \"$pkg\" -- are you offline\? >&2
|
|
end
|
|
" >/dev/null &
|
|
|
|
set pkg_jobs $pkg_jobs (_fisher_jobs --last)
|
|
set expected_pkgs $expected_pkgs "$pkg"
|
|
end
|
|
end
|
|
|
|
if test ! -z "$pkg_jobs"
|
|
_fisher_wait $pkg_jobs
|
|
for pkg in $expected_pkgs
|
|
if test -d "$fisher_config/$pkg"
|
|
set actual_pkgs $actual_pkgs $pkg
|
|
_fisher_pkg_install $fisher_config/$pkg
|
|
end
|
|
end
|
|
end
|
|
|
|
for pkg in $local_pkgs
|
|
set -l path "local/$USER"
|
|
set -l name (echo "$pkg" | command sed 's|^.*/||')
|
|
|
|
command mkdir -p $fisher_config/$path
|
|
command ln -sf $pkg $fisher_config/$path
|
|
|
|
set actual_pkgs $actual_pkgs $path/$name
|
|
_fisher_pkg_install $fisher_config/$path/$name
|
|
end
|
|
|
|
if test ! -z "$actual_pkgs"
|
|
_fisher_pkg_fetch_all (_fisher_pkg_get_deps $actual_pkgs | command sort --unique)
|
|
printf "%s\n" $actual_pkgs
|
|
end
|
|
end
|
|
|
|
function _fisher_pkg_get_deps
|
|
for pkg in $argv
|
|
set -l path $fisher_config/$pkg
|
|
if test ! -d "$path"
|
|
echo $pkg
|
|
else if test -s "$path/fishfile"
|
|
_fisher_pkg_get_deps (_fisher_fishfile_indent < $path/fishfile | _fisher_fishfile_load)
|
|
end
|
|
end
|
|
end
|
|
|
|
function _fisher_pkg_install -a pkg
|
|
set -l name (echo $pkg | command sed "s|^.*/||")
|
|
set -l files $pkg/{functions,completions,conf.d}/* $pkg/*.fish
|
|
for source in $files
|
|
set -l target (echo "$source" | command sed 's|^.*/||')
|
|
switch $source
|
|
case $pkg/conf.d\*
|
|
set target $fisher_path/conf.d/$target
|
|
case $pkg/completions\*
|
|
set target $fisher_path/completions/$target
|
|
case $pkg/{functions,}\*
|
|
switch $target
|
|
case uninstall.fish
|
|
continue
|
|
case init.fish key_bindings.fish
|
|
set target $fisher_path/conf.d/$name\_$target
|
|
case \*
|
|
set target $fisher_path/functions/$target
|
|
end
|
|
end
|
|
echo "linking $target" | command sed "s|$HOME|~|" >&2
|
|
command ln -f $source $target
|
|
switch $target
|
|
case \*.fish
|
|
source $target >/dev/null 2>/dev/null
|
|
end
|
|
end
|
|
end
|
|
|
|
function _fisher_pkg_uninstall -a pkg
|
|
set -l name (echo $pkg | command sed "s|^.*/||")
|
|
set -l files $pkg/{conf.d,completions,functions}/* $pkg/*.fish
|
|
for source in $files
|
|
set -l target (echo "$source" | command sed 's|^.*/||')
|
|
set -l filename (echo "$target" | command sed 's|.fish||')
|
|
switch $source
|
|
case $pkg/conf.d\*
|
|
test "$filename.fish" = "$target"; and emit "$filename"_uninstall
|
|
set target conf.d/$target
|
|
case $pkg/completions\*
|
|
test "$filename.fish" = "$target"; and complete -ec $filename
|
|
set target completions/$target
|
|
case $pkg/{,functions}\*
|
|
test "$filename.fish" = "$target"; and functions -e $filename
|
|
switch $target
|
|
case uninstall.fish
|
|
source $source
|
|
continue
|
|
case init.fish key_bindings.fish
|
|
set target conf.d/$name\_$target
|
|
case \*
|
|
set target functions/$target
|
|
end
|
|
end
|
|
command rm -f $fisher_path/$target
|
|
end
|
|
if not functions -q fish_prompt
|
|
source "$__fish_datadir/functions/fish_prompt.fish"
|
|
end
|
|
end
|
|
|
|
function _fisher_fishfile_indent -a pkgs
|
|
command awk -v PWD=$PWD -v HOME=$HOME -v PKGS="$pkgs" '
|
|
function normalize(s) {
|
|
gsub(/^[ \t]*|[ \t]*$|^https?:\/\/|github\.com\/|\.git$|\/$/, "", s)
|
|
sub(/^\.\//, PWD"/", s)
|
|
sub(HOME, "~", s)
|
|
return s
|
|
}
|
|
function get_pkg_name(s) {
|
|
split(s, tmp, /[@# ]+/)
|
|
return tmp[1]
|
|
}
|
|
BEGIN {
|
|
pkg_count = split(PKGS, pkgs, ";") - 1
|
|
cmd = pkgs[1]
|
|
for (i = 2; i <= pkg_count; i++) {
|
|
pkg_ids[i - 1] = get_pkg_name( pkgs[i] = normalize(pkgs[i]) )
|
|
}
|
|
} {
|
|
if (NF) {
|
|
nl = nl > 0 ? "" : nl
|
|
pkg_id = get_pkg_name( $0 = normalize($0) )
|
|
if (/^#/) print nl$0
|
|
else if (!seen[pkg_id]++) {
|
|
for (i = 1; i < pkg_count; i++) {
|
|
if (pkg_ids[i] == pkg_id) {
|
|
if (cmd == "rm") next
|
|
$0 = pkgs[i + 1]
|
|
break
|
|
}
|
|
}
|
|
print nl$0
|
|
}
|
|
nl = NF
|
|
} else if (nl) nl = (nl > 0 ? "" : nl)"\n"
|
|
}
|
|
END {
|
|
if (cmd == "rm" || pkg_count <= 1) exit
|
|
for (i = 2; i <= pkg_count; i++) {
|
|
if (!seen[pkg_ids[i - 1]]) print pkgs[i]
|
|
}
|
|
}
|
|
'
|
|
end
|
|
|
|
function _fisher_fishfile_load
|
|
command awk -v FS=\# '!/^#/ && NF { print $1 }'
|
|
end
|
|
|
|
function _fisher_status_report
|
|
command awk '
|
|
function msg(res, str, n) {
|
|
return (res ? res ", " : "") str " " n " package" (n > 1 ? "s" : "")
|
|
}
|
|
$1 = $1 - $2 { res = msg(res, "added", $1) }
|
|
$2 { res = msg(res, "updated", $2) }
|
|
$3 = $3 - $2 { res = msg(res, "removed", $3) }
|
|
{ printf((res ? res : "done") " in %.2fs\n", ($4 / 1000)) }
|
|
'
|
|
end
|
|
|
|
function _fisher_jobs
|
|
jobs $argv | command awk -v FS=\t '
|
|
/[0-9]+\t/ {
|
|
jobs[++n] = $1
|
|
} END {
|
|
for (i in jobs) print(jobs[i])
|
|
exit n == 0
|
|
}
|
|
'
|
|
end
|
|
|
|
function _fisher_wait
|
|
while for job in $argv
|
|
contains -- $job (_fisher_jobs); and break
|
|
end
|
|
end
|
|
end
|