[AHK2] 使用#Include-二

发布时间 2023-08-23 18:49:55作者: 落寞的雪

开始

上一次,我们提到了如何使用#Include,以及使用时应注意的问题,现在我们就来解决这个问题。

如何保证资源路径始终是正确的?

思路

我们仿造nodeJs的Path.resolve(),实现一个ahk的resolve()。
但要注意的一点是:

  • 使用静态变量Workdir代替__dirname

因为我们没有这样一个__dirname变量,只有自己造。

接下来,就是实现各种情况的path拼接了,需要考虑的方面十分多,总结包括以下几点:

  • .开头
  • ..开头
  • 以盘符开头
  • 以路径开头
    • 以绝对路径开头
    • 以相对路径开头

同时,还要考虑多参及分隔符统一的情况。

实现

为解决多参问题,我们先分为无、单参和多参进行处理,先处理简单的,在对付难的。
具体细节我便不再多讲,代码都有完整的注释。

需要注意的是,我算法并不好,所以采用的只是简单的循环判断。不过结果肯定没问题:(

resolve()实现

  ; param: paths
  ; ret:   absolute path --> string
  ; desc:  convert to absolute path
  static Resolve(params*) {
    len := params.Length
    ; 单参,默认以当前目录为基准
    if !len || (len == 1 && (params[1] == '' || params[1] == '.')) { ; 无参,参数为空值及.返回当前目录
      return StrReplace(Path.Workdir, '\', Path.Sep)
    } else if (len == 1) {
      if (params[1] ~= '^\.[\\/]') { ; ./开头,直接去除开头./,再规范化后即可返回
        ; 去除开头的./
        _path := Path.Workdir SubStr(params[1], 2)
        ; 统一分隔符
        _path := StrReplace(_path, '/', '\')
        ; 去除末尾 \
        _path := RTrim(_path, '\')
        ; 规范化
        _path := StrReplace(_path, '\', Path.Sep)
        return _path
      }
      else if (params[1] ~= '^\.\.[\\/]') { ; ../ 开头的单参,连接目录路径后放行
        params[1] := Path.Workdir params[1]
      }
      else if (params[1] ~= '^\.\.$') { ; .. 添加sep后放行
        params[1] := Path.Workdir params[1] Path.Sep
      }
      else { ;无法处理
        return StrReplace(Path.Workdir, '\', Path.Sep)
      }
    }
    ; 多参
    for index, param in params {
      if !param ; 跳过空值
        continue
      if param ~= '^[\\/]' { ; 如果以分隔符开头,则清除先前的路径
        _path := param
      }
      else if (index == 1) ; 第一个参数,不加sep
        _path .= param
      else ; 添加分隔符
        _path .= '\' param
    }
    ; 统一分隔符
    _path := StrReplace(_path, '/', '\')
    ; 替换 ..
    while (_path ~= 'i)\.\.') {
      ; 找到..位置
      subStart := InStr(_path, '..')
      ; 找到..前第二个\位置
      subEnd := InStr(_path, '\', , -(StrLen(_path) - subStart) - 3)
      ; 裁剪出待替换字串
      sub := SubStr(_path, subEnd, subStart - subEnd + 2)
      ; MsgBox subStart '--' subEnd '`n' sub '`n' _path
      _path := StrReplace(_path, sub, '')
      if !sub
        throw Error('invalid param! current path--> ' _path)
    }
    ; 替换 .\
    _path := StrReplace(_path, '.\', '')
    ; 去除末尾 \
    _path := RTrim(_path, '\')
    ; 规范化
    _path := StrReplace(_path, '\', Path.Sep)
    ; 判断是否带盘符,无则添加当前盘符
    if _path ~= 'i)^\w:[\\/]'
      return _path
    ; 判断是否添加全路径
    else if _path ~= '^[\\/]' {
      drive := SubStr(Path.Workdir, 1, 1)
      return drive ':' _path
    }
    else {
      scriptDir := StrReplace(Path.Workdir, '/', Path.Sep)
      scriptDir := StrReplace(Path.Workdir, '\', Path.Sep)
      return scriptDir Path.sep _path
    }
  }

测试

以下是测试用例:

其中,注释为正确结果

if A_LineFile == A_ScriptFullPath {
  MsgBox Path.Resolve() ; C:\Users\u\Desktop\AhkPlanE\utils
  MsgBox Path.Resolve('.') ; C:\Users\u\Desktop\AhkPlanE\utils
  MsgBox Path.Resolve('./') ; C:\Users\u\Desktop\AhkPlanE\utils
  MsgBox Path.Resolve('..') ; C:\Users\u\Desktop\AhkPlanE
  MsgBox Path.Resolve('../') ; C:\Users\u\Desktop\AhkPlanE
  MsgBox Path.Resolve('./abc\') ; C:\Users\u\Desktop\AhkPlanE\utils\abc
  MsgBox Path.Resolve('..\abc\..\def')  ; C:\Users\u\Desktop\AhkPlanE\def
  MsgBox Path.Resolve('C:\Users\u\Desktop\AhkPlanE', '../') ; C:\Users\u\Desktop
  MsgBox Path.Resolve('C:\Users\u\Desktop\AhkPlanE', '../res/light/../abc') ; C:\Users\u\Desktop\res\abc
  MsgBox Path.Resolve('/ic', '../a/b', '.\d') ; C:\a\b\d
  MsgBox Path.Resolve('abc', '/ic', '../a/b', '.\d') ; C:\a\b\d
}

join()实现

另外,顺便增加一个简单的拼接方法。

; param: separator | paths
; ret:   path --> string
; desc:  Connect paths using separator
static Join(sep, params*) {
  for index, param in params
    str .= param . sep
  return SubStr(str, 1, -StrLen(sep))
}

静态变量

; 设置当前目录
static Workdir := '' || A_ScriptDir
static Sep := '/'

其中,Sep为resolve统一后的分隔符。

workdir的使用

回到之前的a、b.ahk例子中,现在a脚本的资源引用需要这样书写:

this.AddPicture('yp h30 w50 vPic', Path.Resolve(Path.Workdir, 'res/light.png'))

以a脚本启动时便需要设置workdir变量:

Path.Workdir := Path.Resolve(A_ScriptDir, '../')
PlanEGui.Show({ theme: 'light' })

而b脚本无需修改。

以此便解决了资源引用的问题,只需手动设置Workdir即可(谁叫我们没有呢XD),同时,Resolve()也可在其他地方使用。由测试用例中可见它的功能还是很强大的。