summaryrefslogtreecommitdiff
path: root/functions/fisher.fish
blob: 4759857106edb16214c4c5d64141c4069c4a1f9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
set -g fisher_version 3.2.9

function fisher -a cmd -d "fish package manager"
    set -q XDG_CACHE_HOME; or set XDG_CACHE_HOME ~/.cache
    set -q XDG_CONFIG_HOME; or 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

    set -q fisher_path; or set -g fisher_path $fish_config

    for path in {$fish_config,$fisher_path}/{functions,completions,conf.d} $fisher_cache
        if test ! -d $path
            command mkdir -p $path
        end
    end

    if test ! -e $fisher_path/completions/fisher.fish
        echo "fisher complete" >$fisher_path/completions/fisher.fish
        _fisher_complete
    end

    if test -e $fisher_path/conf.d/fisher.fish
        switch "$version"
            case \*-\*
                command rm -f $fisher_path/conf.d/fisher.fish
            case 2\*
            case \*
                command rm -f $fisher_path/conf.d/fisher.fish
        end
    else
        switch "$version"
            case \*-\*
            case 2\*
                echo "fisher copy-user-key-bindings" >$fisher_path/conf.d/fisher.fish
        end
    end

    switch "$cmd"
        case {,self-}complete
            _fisher_complete
        case copy-user-key-bindings
            _fisher_copy_user_key_bindings
        case ls
            set -e argv[1]
            if test -s "$fisher_path/fishfile"
                set -l file (_fisher_fmt <$fisher_path/fishfile | _fisher_parse -R | command sed "s|@.*||")
                _fisher_ls | _fisher_fmt | command awk -v FILE="$file" "
                    BEGIN { for (n = split(FILE, f); ++i <= n;) file[f[i]] } \$0 in file && /$argv[1]/
                " | command sed "s|^$HOME|~|"
            end
        case self-update
            _fisher_self_update (status -f)
        case self-uninstall
            _fisher_self_uninstall
        case {,-}-v{ersion,}
            echo "fisher version $fisher_version" (status -f | command sed "s|^$HOME|~|")
        case {,-}-h{elp,}
            _fisher_help
        case ""
            _fisher_commit --
        case add rm
            if not isatty
                while read -l arg
                    set argv $argv $arg
                end
            end

            if test (count $argv) = 1
                echo "fisher: invalid number of arguments" >&2
                _fisher_help >&2
                return 1
            end

            _fisher_commit $argv
        case \*
            echo "fisher: unknown flag or command \"$cmd\"" >&2
            _fisher_help >&2
            return 1
    end
end

function _fisher_complete
    complete -ec fisher
    complete -xc fisher -n __fish_use_subcommand -a add -d "Add packages"
    complete -xc fisher -n __fish_use_subcommand -a rm -d "Remove packages"
    complete -xc fisher -n __fish_use_subcommand -a ls -d "List installed packages matching REGEX"
    complete -xc fisher -n __fish_use_subcommand -a --help -d "Show usage help"
    complete -xc fisher -n __fish_use_subcommand -a --version -d "$fisher_version"
    complete -xc fisher -n __fish_use_subcommand -a self-update -d "Update to the latest version"
    for pkg in (fisher ls)
        complete -xc fisher -n "__fish_seen_subcommand_from rm" -a $pkg
    end
end

function _fisher_copy_user_key_bindings
    if functions -q fish_user_key_bindings
        functions -c fish_user_key_bindings fish_user_key_bindings_copy
    end
    function fish_user_key_bindings
        for file in $fisher_path/conf.d/*_key_bindings.fish
            source $file >/dev/null 2>/dev/null
        end
        if functions -q fish_user_key_bindings_copy
            fish_user_key_bindings_copy
        end
    end
end

function _fisher_ls
    for pkg in $fisher_config/*/*/*
        command readlink $pkg; or echo $pkg
    end
end

function _fisher_fmt
    command sed "s|^[[:space:]]*||;s|^$fisher_config/||;s|^~|$HOME|;s|^\.\/*|$PWD/|;s|^https*:/*||;s|^github\.com/||;s|/*\$||"
end

function _fisher_help
    echo "usage: fisher add <package...>     Add packages"
    echo "       fisher rm  <package...>     Remove packages"
    echo "       fisher                      Update all packages"
    echo "       fisher ls  [<regex>]        List installed packages matching <regex>"
    echo "       fisher --help               Show this help"
    echo "       fisher --version            Show the current version"
    echo "       fisher self-update          Update to the latest version"
    echo "       fisher self-uninstall       Uninstall from your system"
    echo "examples:"
    echo "       fisher add jethrokuan/z rafaelrinaldi/pure"
    echo "       fisher add gitlab.com/foo/bar@v2"
    echo "       fisher add ~/path/to/local/pkg"
    echo "       fisher add <file"
    echo "       fisher rm rafaelrinaldi/pure"
    echo "       fisher ls | fisher rm"
    echo "       fisher ls fish-\*"
end

function _fisher_self_update -a file
    set -l url "https://raw.githubusercontent.com/jorgebucaran/fisher/master/fisher.fish"
    echo "fetching $url" >&2
    command curl -s "$url?nocache" >$file.

    set -l next_version (command awk '{ print $4 } { exit }' <$file.)
    switch "$next_version"
        case "" $fisher_version
            command rm -f $file.
            if test -z "$next_version"
                echo "fisher: 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 to fisher $fisher_version -- hooray!" >&2
            _fisher_complete
    end
end

function _fisher_self_uninstall
    for pkg in (_fisher_ls)
        _fisher_rm $pkg
    end

    for file in $fisher_cache $fisher_config $fisher_path/{functions,completions,conf.d}/fisher.fish $fisher_path/fishfile
        echo "removing $file"
        command rm -Rf $file 2>/dev/null
    end | command sed "s|$HOME|~|" >&2

    for name in (set -n | command awk '/^fisher_/')
        set -e "$name"
    end

    functions -e (functions -a | command awk '/^_fisher/') fisher
    complete -c fisher --erase
end

function _fisher_commit -a cmd
    set -e argv[1]
    set -l elapsed (_fisher_now)
    set -l fishfile $fisher_path/fishfile

    if test ! -e "$fishfile"
        command touch $fishfile
        echo "created new fishfile in $fishfile" | command sed "s|$HOME|~|" >&2
    end

    set -l old_pkgs (_fisher_ls | _fisher_fmt)
    for pkg in (_fisher_ls)
        _fisher_rm $pkg
    end
    command rm -Rf $fisher_config
    command mkdir -p $fisher_config

    set -l next_pkgs (_fisher_fmt <$fishfile | _fisher_parse -R $cmd (printf "%s\n" $argv | _fisher_fmt))
    set -l actual_pkgs (_fisher_fetch $next_pkgs)
    set -l updated_pkgs
    for pkg in $old_pkgs
        if contains -- $pkg $actual_pkgs
            set updated_pkgs $updated_pkgs $pkg
        end
    end

    if test -z "$actual_pkgs$updated_pkgs$old_pkgs$next_pkgs"
        echo "fisher: nothing to commit -- try adding some packages" >&2
        return 1
    end

    set -l out_pkgs
    if test "$cmd" = "rm"
        set out_pkgs $next_pkgs
    else
        for pkg in $next_pkgs
            if contains -- (echo $pkg | command sed "s|@.*||") $actual_pkgs
                set out_pkgs $out_pkgs $pkg
            end
        end
    end

    printf "%s\n" (_fisher_fmt <$fishfile | _fisher_parse -W $cmd $out_pkgs | command sed "s|^$HOME|~|") >$fishfile

    _fisher_complete

    command awk -v A=(count $actual_pkgs) -v U=(count $updated_pkgs) -v O=(count $old_pkgs) -v E=(_fisher_now $elapsed) '
        BEGIN {
            res = fmt("removed", O - U, fmt("updated", U, fmt("added", A - U)))
            printf((res ? res : "done") " in %.2fs\n", E / 1000)
        }
        function fmt(action, n, s) {
            return n ? (s ? s ", " : s) action " " n " package" (n > 1 ? "s" : "") : s
        }
    ' >&2
end

function _fisher_parse -a mode cmd
    set -e argv[1..2]
    command awk -v FS="[[:space:]]*#+" -v MODE="$mode" -v CMD="$cmd" -v ARGSTR="$argv" '
        BEGIN {
            for (n = split(ARGSTR, a, " "); i++ < n;) pkgs[getkey(a[i])] = a[i]
        }
        !NF { next } { k = getkey($1) }
        MODE == "-R" && !(k in pkgs) && $0 = $1
        MODE == "-W" && (/^#/ || k in pkgs || CMD != "rm") { print pkgs[k] (sub($1, "") ? $0 : "") }
        MODE == "-W" || CMD == "rm" { delete pkgs[k] }
        END {
            for (k in pkgs) {
                if (CMD != "rm" || MODE == "-W") print pkgs[k]
                else print "fisher: cannot remove \""k"\" -- package is not in fishfile" > "/dev/stderr"
            }
        }
        function getkey(s,  a) {
            return (split(s, a, /@+|:/) > 2) ? a[2]"/"a[1]"/"a[3] : a[1]
        }
    '
end

function _fisher_fetch
    set -l pkg_jobs
    set -l out_pkgs
    set -l next_pkgs
    set -l local_pkgs
    set -q fisher_user_api_token; and set -l curl_opts -u $fisher_user_api_token

    for pkg in $argv
        switch $pkg
            case \~\* /\*
                set -l path (echo "$pkg" | command sed "s|^~|$HOME|")
                if test -e "$path"
                    set local_pkgs $local_pkgs $path
                else
                    echo "fisher: cannot add \"$pkg\" -- is this a valid file?" >&2
                end
                continue
        end

        command awk -v PKG="$pkg" -v FS=/ '
            BEGIN {
                if (split(PKG, tmp, /@+|:/) > 2) {
                    if (tmp[4]) sub("@"tmp[4], "", PKG)
                    print PKG "\t" tmp[2]"/"tmp[1]"/"tmp[3] "\t" (tmp[4] ? tmp[4] : "master")
                } else {
                    pkg = split(PKG, _, "/") <= 2 ? "github.com/"tmp[1] : tmp[1]
                    tag = tmp[2] ? tmp[2] : "master"
                    print (\
                        pkg ~ /^github/ ? "https://codeload."pkg"/tar.gz/"tag : \
                        pkg ~ /^gitlab/ ? "https://"pkg"/-/archive/"tag"/"tmp[split(pkg, tmp, "/")]"-"tag".tar.gz" : \
                        pkg ~ /^bitbucket/ ? "https://"pkg"/get/"tag".tar.gz" : pkg \
                    ) "\t" pkg
                }
            }
        ' | read -l url pkg branch

        if test ! -d "$fisher_config/$pkg"
            fish -c "
                echo fetching $url >&2
                command mkdir -p $fisher_config/$pkg $fisher_cache/(command dirname $pkg)
                if test ! -z \"$branch\"
                     command git clone $url $fisher_config/$pkg --branch $branch --depth 1 2>/dev/null
                     or echo fisher: cannot clone \"$url\" -- is this a valid url\? >&2
                else if command curl $curl_opts -Ss -w \"\" $url 2>&1 | command tar -xzf- -C $fisher_config/$pkg 2>/dev/null
                    command rm -Rf $fisher_cache/$pkg
                    command mv -f $fisher_config/$pkg/* $fisher_cache/$pkg
                    command rm -Rf $fisher_config/$pkg
                    command cp -Rf {$fisher_cache,$fisher_config}/$pkg
                else if test -d \"$fisher_cache/$pkg\"
                    echo fisher: cannot connect to server -- looking in \"$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 fisher: cannot add \"$pkg\" -- is this a valid package\? >&2
                end
            " >/dev/null &
            set pkg_jobs $pkg_jobs (_fisher_jobs --last)
            set next_pkgs $next_pkgs "$fisher_config/$pkg"
        end
    end

    if set -q pkg_jobs[1]
        while for job in $pkg_jobs
                contains -- $job (_fisher_jobs); and break
            end
        end
        for pkg in $next_pkgs
            if test -d "$pkg"
                set out_pkgs $out_pkgs $pkg
                _fisher_add $pkg
            end
        end
    end

    set -l local_prefix $fisher_config/local/$USER
    if test ! -d "$local_prefix"
        command mkdir -p $local_prefix
    end
    for pkg in $local_pkgs
        set -l target $local_prefix/(command basename $pkg)
        if test ! -L "$target"
            command ln -sf $pkg $target
            set out_pkgs $out_pkgs $pkg
            _fisher_add $pkg --link
        end
    end

    if set -q out_pkgs[1]
        _fisher_fetch (
            for pkg in $out_pkgs
                if test -s "$pkg/fishfile"
                    _fisher_fmt <$pkg/fishfile | _fisher_parse -R
                end
            end)
        printf "%s\n" $out_pkgs | _fisher_fmt
    end
end

function _fisher_add -a pkg opts
    for src in $pkg/{functions,completions,conf.d}/**.* $pkg/*.fish
        set -l target (command basename $src)
        switch $src
            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,key_bindings}.fish
                        set target $fisher_path/conf.d/(command basename $pkg)\_$target
                    case \*
                        set target $fisher_path/functions/$target
                end
        end
        echo "linking $target" | command sed "s|$HOME|~|" >&2
        if set -q opts[1]
            command ln -sf $src $target
        else
            command cp -f $src $target
        end
        switch $target
            case \*.fish
                source $target >/dev/null 2>/dev/null
        end
    end
end

function _fisher_rm -a pkg
    for src in $pkg/{conf.d,completions,functions}/**.* $pkg/*.fish
        set -l target (command basename $src)
        set -l filename (command basename $target .fish)
        switch $src
            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 $src
                        continue
                    case {init,key_bindings}.fish
                        set target conf.d/(command basename $pkg)\_$target
                    case \*
                        set target functions/$target
                end
        end
        command rm -f $fisher_path/$target
    end
    if not functions -q fish_prompt
        source "$__fish_datadir$__fish_data_dir/functions/fish_prompt.fish"
    end
end

function _fisher_jobs
    jobs $argv | command awk '/^[0-9]+\t/ { print $1 }'
end

function _fisher_now -a elapsed
    switch (command uname)
        case Darwin \*BSD
            command perl -MTime::HiRes -e 'printf("%.0f\n", (Time::HiRes::time() * 1000) - $ARGV[0])' $elapsed
        case \*
            math (command date "+%s%3N") - "0$elapsed"
    end
end