BruceChen7 / gitblog

My blog
6 stars 1 forks source link

z.lua解析 #28

Open BruceChen7 opened 3 years ago

BruceChen7 commented 3 years ago

参考资料

这是一个用lua来执行快速跳转的工具,来替代z.sh,执行速度快。

分析

eval "$(lua /path/to/z.lua  --init bash once enhanced)"   # BASH 初始化

我们执行$(lua /path/to/z.lua --init bash once enanced) 输出的脚本是

ZLUA_SCRIPT="/usr/local/bin/z.lua"
ZLUA_LUAEXE="/usr/bin/lua"

_zlua() {
        local arg_mode=""
        local arg_type=""
        local arg_subdir=""
        local arg_inter=""
        local arg_strip=""
        if [ "$1" = "--add" ]; then
                shift
                _ZL_RANDOM="$RANDOM" "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --add "$@"
                return
        elif [ "$1" = "--complete" ]; then
                shift
                "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --complete "$@"
                return
        fi
        while [ "$1" ]; do
                case "$1" in
                        -l) local arg_mode="-l" ;;
                        -e) local arg_mode="-e" ;;
                        -x) local arg_mode="-x" ;;
                        -t) local arg_type="-t" ;;
                        -r) local arg_type="-r" ;;
                        -c) local arg_subdir="-c" ;;
                        -s) local arg_strip="-s" ;;
                        -i) local arg_inter="-i" ;;
                        -I) local arg_inter="-I" ;;
                        -h|--help) local arg_mode="-h" ;;
                        --purge) local arg_mode="--purge" ;;
                        *) break ;;
                esac
                shift
        done
        if [ "$arg_mode" = "-h" ] || [ "$arg_mode" = "--purge" ]; then
                "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode
        elif [ "$arg_mode" = "-l" ] || [ "$#" -eq 0 ]; then
                "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip "$@"
        elif [ -n "$arg_mode" ]; then
                "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode $arg_subdir $arg_type $arg_inter "$@"
        else
                local zdest=$("$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --cd $arg_type $arg_subdir $arg_inter "$@")
                if [ -n "$zdest" ] && [ -d "$zdest" ]; then
                        if [ -z "$_ZL_CD" ]; then
                                builtin cd "$zdest"
                        else
                                $_ZL_CD "$zdest"
                        fi
                        if [ -n "$_ZL_ECHO" ]; then pwd; fi
                fi
        fi
}
# alias ${_ZL_CMD:-z}='_zlua 2>&1'
alias ${_ZL_CMD:-z}='_zlua'

_zlua_precmd() {
    [ "$_ZL_PREVIOUS_PWD" = "$PWD" ] && return
    _ZL_PREVIOUS_PWD="$PWD"
    (_zlua --add "$PWD" 2> /dev/null &)
}
case "$PROMPT_COMMAND" in
        *_zlua_precmd*) ;;
        *) PROMPT_COMMAND="_zlua_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;;
esac

if [ -n "$BASH_VERSION" ]; then
        complete -o filenames -C '_zlua --complete "$COMP_LINE"' ${_ZL_CMD:-z}
fi

_zlua _zlua是一段胶水代码,其做了两件事情

PROMPT_COMMAND就是说每执行一个命令前,PROMPT_COMMAND 里面先执行,然后执行PROMPT

_zlua --add

首先定义了z.lua脚本的位置和解释器的位置。然后定义shell函数_z_lua。看下最关键的信息:

# 如果是使用bash
if [ -n "$BASH_VERSION" ]; then
        #使用complete来不全文件列表
        #-C用来产生候选项的命令,会调用一个sub shell来执行
        complete -o filenames -C '_zlua --complete "$COMP_LINE"' ${_ZL_CMD:-z}
fi

这里可以查看$COMP_LINE的含义,这里的概念programmable completion.这里我们可以理解成,当我们在终端打z xxx tab键的时候, 这个变量\$COMP_LINE就是我们打入的字符串,接着, 叫做我们可以看_zlua,实际上直接跳到

"$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --complete "$@"

然后开始执行z.lua的逻辑,

z.lua的complete逻辑

进入z.lua的逻辑是,在命令行传入了--complete的和 "$@",

function main(argv):
   ...
   elseif options['--complete'] then                          
      local line = args[1] and args[1] or ''                 
      local head = line:sub(Z_CMD:len()+1):gsub('^%s+', '')  
      local M = z_match({head}, Z_METHOD, Z_SUBDIR)          
      -- 打印所有的item                                      
      for _, item in pairs(M) do                             
          print(item.name)                                   
      end
   ....
end

z.lua添加path

这部分逻辑比较清晰:


注意这里有更新数据文件中path的逻辑,这部分逻辑在data_insert()中体现,这里由一个数据老化的概念
* 总的访问次数超过了5000次(默认),那么 * 0.9后清楚不足1,清除这个path
```lua
 function data_insert(M, filename)
     local i = 1
     local sumscore = 0
     for i = 1, #M do
         local item = M[i]
         sumscore = sumscore + item.rank
     end
     if sumscore >= MAX_AGE then
         local X = {}
         for i = 1, #M do
             local item = M[i]
             item.rank = item.rank * 0.9
             -- 清除不足1的rank
             if item.rank >= 1.0 then
                 table.insert(X, item)
             end
         end
         M = X
     end
     local nocase = path_case_insensitive()
     local name = filename
     local key = nocase and string.lower(name) or name
     local find = false
     local current = os.time()
     for i = 1, #M do
         local item = M[i]
         if not nocase then
             if name == item.name then
                 item.rank = item.rank + 1
                 item.time = current
                 find = true
                 break
             end
         else
             -- 存在,则rank + 1
             -- 更新访问时间戳
             if key == string.lower(item.name) then
                 item.rank = item.rank + 1
                 item.time = current
                 find = true
                 break
             end
         end
     end
     --初始化时frecent为rank
     if not find then
        local item = {}
        item.name = name
        item.rank = 1
        item.time = current
        item.frecent = item.rank
        table.insert(M, item)
     end
 return M

注意这里的frecent值默认时rank的值,也就是1,我们发现在切换工作目录时,也就是调用data_insert时,并没有frecent值进行更新,只是在不存在该路径的时候,给了一个默认值,那么什么时候更新这个frecent值呢,在match的时候。

z_match

function z_match(patterns, method, subdir)
    patterns = patterns ~= nil and patterns or {}
    -- 默认支持几种匹配的pattern
    method = method ~= nil and method or 'frecent'
    subdir = subdir ~= nil and subdir or false
    local M = data_load(DATA_FILE)
    -- 从数据集中选择匹配的path
    M = data_select(M, patterns, false)
    M = data_filter(M)
    if Z_MATCHNAME then
        local N = data_select(M, patterns, true)
        N = data_filter(N)
        if #N > 0 then
            M = N
        end
    end
    -- 在匹配时,进行对frecent更新
    M = data_update_frecent(M)
    -- 匹配算法如果时按照时间
    if method == 'time' then
        current = os.time()
        for _, item in pairs(M) do
            item.score = item.time - current
        end
    elseif method == 'rank' then
        --根据分值来排名
        for _, item in pairs(M) do
            item.score = item.rank
        end
    else
        for _, item in pairs(M) do
            -- 默认使用frecent值
            item.score = item.frecent
        end
    end
    --按照分值排序
    table.sort(M, function (a, b) return a.score > b.score end)
    -- 获取当前的工作目录
    local pwd = (PWD == nil or PWD == '') and os.getenv('PWD') or PWD
    if pwd == nil or pwd == '' then
        pwd = os.pwd()
    end
    if pwd ~= '' and pwd ~= nil then
        --如果是子目录模式
        if subdir then
            local N = {}
            --获取满足当前目录中的子目录,并过滤
            for _, item in pairs(M) do
                if os.path.subdir(pwd, item.name) then
                    table.insert(N, item)
                end
            end
            M = N
        end
        if Z_SKIPPWD then
            local N = {}
            local key = windows and string.lower(pwd) or pwd
            for _, item in pairs(M) do
                local match = false
                local name = windows and string.lower(item.name) or item.name
                if name ~= key then
                        table.insert(N, item)
                end
            end
            M = N
        end
    end
    return M
end

match的逻辑比较简单