ttscoff / fish_files

140 stars 15 forks source link

Fully fish `cdt` #1

Open halostatue opened 4 years ago

halostatue commented 4 years ago

I don’t use your TagFiler script (I’m not that organized), but I was looking at your bash_scripts/cdt.bash and came up with the following that seems like it would eliminate the bash part of that setup.

This is written and mildly with fish 3.0.2, and there are changes coming in 3.1 which will make subprocess capture better overall, but since I don’t use TagFiler, I’m not the best tester of this. It was an interesting exercise, though:

function cdt::usage
    echo "Usage: cdt TERM+

Changes directory based on tagged folders. If the first search term begins
with a `+`, a =Context folder is located."
end

function cdt::fuzzy
    string replace -r -- '^\+' '' $argv |
    string replace -ar -- '[:.]' '' |
    string replace -ar -- '([[:alpha:]])' '$1*'
end

function cdt::mdfind -a term
    argparse -x t,c -N 1 'c/context' 't/tag' 'r/root=' -- $argv

    set -l folder "kMDItemContentType = 'public.folder'"

    set -l root ~
    set -q _flag_root
    and set root $_flag_root

    set -l term (cdt::fuzzy $term)

    if set -q _flag_context
        set term "=$term"
    else
        set term "@$term"
    end

    set -l tags "kMDItemUserTags = '$term'c"

    mdfind -0 -onlyin (string escape $root) "$tags and $folder"
end

function cdt::find -a root term
    find $root -maxdepth 3 -type d -iname (cdt::fuzzy $term) -print0
end

function cdt::shortest
    set -l list (cat - | string split0)
    set -l shortest $list[1]
    set -l sl (string length $shortest)

    if test (count $list) -gt 1
        for found in $list[2..-1]
            set -l fl (string length $fl)
            if test $fl -lt $sl
                set shortest $found
                set sl $fl
            end
        end
    end

    test -z $shortest
    and return 1

    echo $shortest
    return 0
end

function cdt -d 'cd to context-tagged folders'
    argparse -N 1 'h/help' -- $argv

    set -q _flag_help
    and begin
        cdt::usage 1>&2
        return 0
    end

    # Normalize terms, which may either be separate words or joined by colons, or
    # a mix of the two.
    set -l terms (string replace -a -- ' ' : $argv | string split :)
    set -l root ~

    # If the first term starts with a `+`, it’s a context search.
    if string match -qr -- '^\+' $terms[1]
        set root (cdt::mdfind -c $terms[1] | string split0)[1]
        set -e terms[1]
    end

    for term in $terms
        set -l sub ''

        # If the term starts with a dot, treat it as a directory name only search
        if string match -qr -- '^\.'
            set sub (cdt::find $root $term | cdt::shortest)
        else
            set sub (cdt::mdfind -r $root -t $term)
        end

        test -z $sub
        and set sub (cdt::find $root $term | cdt::shortest)

        test -d $sub
        set root $sub
    end

    if test -d $root -a $root != ~
      cd $root
      return 0
    else
      echo 1>&2 'No target directory found.'
      return 1
    end
end
ttscoff commented 2 years ago

Thanks, I'll test this out. It was one of those scripts that was working fine running as a bash script, and I don't use it frequently, so it wasn't at the top of my list for porting. But this saves me some time :)