1 简介

utools是一个生产力工具,自由集成丰富插件,快速匹配「场景功能」,用完即走。
可以先看一下官网,和一个视频初步了解下utools:
官方文档:主页技术文档
介绍视频:我心目中最佳的国产软件之一:全平台装机必备。_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

以ocrg功能为例,补充介绍下用法。
需要先装插件,
image.png
推荐“长按鼠标右键”打开超级面板,
image.png
然后打开一张图片,长按右键,就有ocr功能出来了。
或者直接打开ocr功能,然后把要识别的内容截图粘贴进去。

即utools还可以代替天若ocr、文件批量重命名等一堆工具。

划词长按右键,也自带翻译功能。

可以设置一些常用功能的全局快捷键
image.png

2 快捷命令

utools有个「快捷命令」的插件:
utools - 图4
可以用python等其他编程语言,来扩展开发自己的工具库插件。
因为我自己是python的重度用户,而且也积累了pyxllib、pyxlpr等工具包;
用「快捷命令」,就可以把windows等系统上各种应用场景,和我的python工具包做关联,实现很多强大的功能。
所以本篇文章,主要是讲「快捷命令」的原理、开发、应用。

首先要新建自己的“快捷命令”: 然后我们来详细讲解下这个窗口各个设置的含义
utools - 图5

2.1 匹配

在什么场景下会触发出该「命令」功能菜单。
一共五种,本质上只有四种,第五种是前四种的组合:

匹配模式 解释 一些功能应用场景例子 latex编校工作中的例子
关键字 Alt+Enter打开utools后,敲功能名使用。 在任意场景,随时打开find(相当于everything)查找功能等各种小工具,用完即走。查找功能的过程方便,支持拼音首字母缩写等等。 相当于存在winr目录里的一些脚本,然后输入texpng.py、bc.py,就可以执行对应脚本。用「快捷命令」来做的好处是不用添加winr环境变量,而且还有subinput等很多扩展功能,使用更加灵活,交互更加方便,详见后文讲解。
正则/划词 选中一段文本后,打开满足对应正则匹配的功能菜单。 划词翻译;匹配到如果是数学表达式,自动计算结果。 原来用pyqt写的剪切板监控工具,可以复制文本后,分析规则,按不同的需求进行自动处理。
窗口/进程 在资源浏览器(explorer.exe)、chrome浏览器、OneNote等等各种软件、窗口中,打开对应的功能菜单。 将目录下的图片进行压缩减小体积。 可以对texpng.py进行扩展,在当前目录打开执行,不用输入待处理的目录路径,自动将目录下所有的pdf生成出png图片。
复制/选中文件 选中部分文件、目录后进行处理。(在everything界面,选中的文件可以是不同目录里的) 只对选中的图片进行压缩减小体积。 相当于我原来写的把py功能加进右键菜单,比如文件备份功能。不过那个还要改注册表,麻烦多了。有很多需要选中部分文件、目录进行处理的功能,都可以用这个模式。
专业模式 高级模式,可以通过配置文件来实现上述所有情况的匹配。 比如图片压缩功能,① 即可以当一个软件打开后,拖入要处理的图片,② 也可以在一个目录打开处理目录下所有图片,③ 还可以对选中的部分图片处理。那么不需要写三个接口,可以用专业模式来匹配。

看utools官方文档,还会看到两种模式:
img: 图片匹配,试不出来,但可以用files代替
over: 无匹配,可以用全匹配正则代替

img可以用选中文件代替,over可以用正则匹配代替,这两类不是必须的。

2.2 配置

不同的匹配模式,对应的配置方法是不同的,在不同场景,不是每个功能每次都要显示出来。
比如“图片压缩功能”,如果选中的是“txt”文件,就不应该弹出这个功能命令;
再比如Beyond Compare对比功能,只有选中2个文件时才弹出,一个文件或太多文件都没必要进行bc比较。
这些精细的控制,可以用“配置”项来完成。

匹配模式 类型:解释 补充说明
关键字 关键字:多个关键字用逗号隔开
正则/划词 正则:匹配文本的正则,如/.*?\.exe$/i 这个正则是js的语法,也就是比py的正则,前后多了一个/,并且在末尾设置模式,例如i是不区分大小写。
窗口/进程 进程:多个窗口进程逗号隔开,留空匹配所有窗口 比如很多功能只是在explorer.exe资源浏览器中才是用的。在OneNote等窗口不触发该功能。
复制/选中文件 正则:匹配文件的正则,如/.*?\.exe$/i
专业模式 配置:等效于 features.cmds 可以用一个json表达出上述4种的复杂组合规则,详细可以看官方关于features.cmds的解释: 完整配置 | uTools。不太懂也没关系,后面会有示例代码。

2.3 说明/标签/环境/图标

这几个项目就比较简单了,一起说。

说明:该「命令」的功能详细介绍
标签:如果开发的「命令」比较多,可以加上标签分组管理
环境:所使用的脚本语言,比如使用python脚本
图标:注意可以设置命令图标

2.4 变量

不同「匹配」模式,变量也有些许差别,写python的时候,可以这样引用内容:

  1. cmds = dict()
  2. # 通用属性
  3. cmds['isWin'] = {{isWin}} # 是否Window系统, 返回1或0
  4. cmds['LocalId'] = '{{LocalId}}' # 本机唯一ID
  5. cmds['BrowserUrl'] = r'''{{BrowserUrl}}''' # 浏览器当前链接
  6. cmds['ClipText'] = r'''{{ClipText}}''' # 剪切板的文本(为了避免出bug,建议用 pyperclip.paste().replace('\r\n', '\n') 实现 )
  7. cmds['subinput'] = r'''{{subinput}}''' # 子输入框的文本
  8. cmds['type'] = '{{type}}' # 专业模式的type
  9. cmds['payload'] = r'''{{payload}}''' # 专业模式的payload, JSON格式字符串
  10. # 划词可用
  11. cmds['input'] = r'''{{input}}''' # 主输入框的文本
  12. # 窗口可用
  13. cmds['pwd'] = '{{pwd}}' # 文件管理器当前目录
  14. cmds['WindowInfo'] = r'''{{WindowInfo}}''' # 当前窗口信息, JSON格式字符串
  15. # 文件可用
  16. cmds['MatchedFiles'] = r'''{{MatchedFiles}}''' # 匹配的文件,JSON格式字符串

注意划词、窗口、文件三种模式,都有自己专用的一些变量,在其他模式不能写出来,否则「命令」会报错保存不了。
但「专业」模式比较特别,可以使用所有变量,但也只是不会报错而已,例如“划词”模式是不存在MatchedFiles变量的,实际会以普通的’{{MatchedFiles}}’字符串传入。

注意我上述的python代码写法。
因为“变量”的实现本质,是用{{var}}的占位符来表示,在执行的时候,会将这些特殊符号直接宏展开,
然后得到一个新的py脚本文件,存储在临时文件夹里:C:\Users\chen\AppData\Local\Temp\quickcommandTempFile.py,
最后实际就是运行这个脚本。

这样的宏展开机制,有个隐患问题,如果内容本身包含了单引号,我们还额外用了单引号,整个py脚本语法解释是会出错的。
所以为了最大限度减小出错概率,我特地用r字符串、三引号模式来保存原始宏展开内容。

其中有些数值比较特别,
isWin本身是数字,所以没用引号;
LocalId是一串普通字母数字构成的,不会有歧义,所以用了单引号。
payload、WindowInfo、MatchedFiles等有时候是json结构数据,这个没关系,先存成字符串,可以后面代码再解析json结构。

补充说明:
窗口模式,还有个SelectFile变量,这个命令说是linux用不了,但是我实测在window也用不了,所以上述就没有讲解使用该变量。
这个变量无所谓的,没什么重要。

各种模式下,变量值的详细示例:

序号 属性 关键字 划词 窗口 文件 专业
1 isWin
(是否Window系统, 返回1或0)


1
2 LocalId
(本机唯一ID)
cfaf836ce6dc5930a6268d27f05612bf
3 BrowserUrl
(如果在浏览器窗口,可以获得当前网址链接)
https://www.baidu.com undefined undefined undefined
4 ClipText
(剪切板内容)
1\t\n\t2\n\t5\n3\t4\n \n百度热榜
(划词操作默认会先复制到剪切板)
本质上是把代码宏展开,在这里运行”C:\Users\chen\AppData\Local\Temp\quickcommandTempFile.py”。 剪切板的文本,为了避免出bug,建议用 pyperclip.paste().replace(‘\r\n’, ‘\n’) 等价实现 专业模式可以用所有属性名,在某些匹配模式下,没有对应宏的不展开
5 subinput
(启动功能后,额外输入的内容)





6 type(匹配类型) text regex window files
7 payload
(内容同专有属性)
快捷命令/关键字 百度热榜
(input内容默认会strip掉两边空白)
{‘id’: 67464, ‘class’: ‘Framework::CFrame’, ‘title’: ‘w210329: - OneNote’, ‘x’: 56, ‘y’: -8, ‘width’: 1872, ‘height’: 1096, ‘appPath’: ‘C:\Program Files\Microsoft Office\root\Office16\ONENOTE.EXE’, ‘pid’: 11844, ‘app’: ‘ONENOTE.EXE’} [{‘isFile‘: False, ‘isDirectory‘: True, ‘name‘: ‘组会’, ‘path‘: ‘D:\Calibre\paper\组会’}, {‘isFile’: …}]
8 专有属性
(从工程角度,推荐尽可能用专有属性名获取数据,而不是payload)

input=百度热榜 WindowInfo,
kwargs[‘WindowInfo’] = json.loads(r’’’{{WindowInfo}}’’’.encode(‘utf8’))
MatchedFiles,有时候会出现D:\\\的情况,有小bug,没payload稳定,使用时推荐写:kwargs[‘MatchedFiles’] =json.loads(r’’’{{payload}}’’’.encode(‘utf8’)) {{input}},不存在的属性没有宏展开就会存储原始字符串内容
9 其他属性

pwd=C:\\Users\\chen\\desktop
SelectFile,我自己测,这个参数在win也不能用


2.5 脚本/输出/编码/参数

最后的脚本就没什么好讲啦,如果是py,就在这里写py代码就行了。
这个使用的是本机的py解释器环境,如果按照了各种包,都可以用。
“输出”有多种格式,读者自己试一下就知道效果了。
“编码设置”默认可以不管;可以设“脚本参数”。

2.6 demo/查看“快捷命令”各种匹配模式下支持的各参数值

我们来做一个可以在各种匹配模式下,查看各变量值的工具,基本配置如下:
utools - 图6
具体的“配置”json:

[
    "快捷命令/关键词",
    {
        "type":"regex",
        "label":"快捷命令/划词",
        "match":"/.{1,}/i"
    },
    {
        "type":"window",
        "label":"快捷命令/窗口"
    },
    {
        "type":"files",
        "label":"快捷命令/文件"
    }
]

脚本:

from pyxllib.basic import TicToc
from pyxllib.utools import UtoolsBase

cmds = dict()
# 通用属性
cmds['isWin'] = {{isWin}}  # 是否Window系统, 返回1或0
cmds['LocalId'] = '{{LocalId}}'  # 本机唯一ID
cmds['BrowserUrl'] = r'''{{BrowserUrl}}'''  # 浏览器当前链接
cmds['ClipText'] = r'''{{ClipText}}'''  # 剪切板的文本(为了避免出bug,建议用 pyperclip.paste().replace('\r\n', '\n') 实现 )
cmds['subinput'] = r'''{{subinput}}'''  # 子输入框的文本
cmds['type'] = '{{type}}'  # 专业模式的type
cmds['payload'] = r'''{{payload}}'''  # 专业模式的payload, JSON格式字符串

# 划词可用
cmds['input'] = r'''{{input}}'''  # 主输入框的文本

# 窗口可用
cmds['pwd'] = '{{pwd}}'  # 文件管理器当前目录
cmds['WindowInfo'] = r'''{{WindowInfo}}'''  # 当前窗口信息, JSON格式字符串

# 文件可用
cmds['MatchedFiles'] = r'''{{MatchedFiles}}'''  # 匹配的文件,JSON格式字符串
with TicToc(__name__):
    UtoolsBase(cmds, outfmt='html').check_cmds()

注意要安装pip install pyxllib>=0.0.71,有版本问题的可以用anaconda来管理。
另外注意,任何改动,包括pyxllib,脚本都会第一时间更新,不需要重启utools。

运行效果,以表格的形式显示「快捷命令」支持的变量的值:
image.png
“2021-04-07 20:51:41”不是乱码,而是命令行里显示彩色的,这里只支持纯文本就变成这种奇怪的效果了,
我觉得问题不大,就暂时没改。不爽的人可以把with TicToc删掉。

2.7 有导入/导出/分享功能

注意右下角菜单,具体用法我这里就不累赘了。

3 通用文件批处理功能

3.1 工具接口设计理念

前面显示变量值的功能看似简单,但展示了我关于 「快捷命令」和python如何结合的一些重要框架理念。

  1. 首先匹配模式可以无脑选“专业”模式,只要掌握专业模式的匹配规则写法就行了。
    1. 所有其他模式的规则,都可以有等价的专业模式匹配写法
    2. 而且专业模式更加精细,可以有更多的参数可以控制,比如控制选中文件的数量为多少才激活
  2. 专业模式能获取所有参数值,然后可以无脑打包为kwargs的形式传入给python接口
    1. 变量中有”type”,如果需要,可以在py代码里再判断type后分类处理
    2. 这样可以把复杂性都集中到py去处理,而不用每种匹配模式都新建一个「快捷命令」,多杂难维护
    3. 打包的时候建议存储使用原始值,在py代码里再解析json等
    4. ClipText比较特别,容易出bug,可以选择不取,而在py代码用pyperclip.paste()等价方式获取
  3. 使用上述设计理念,可以最大限度发挥「快捷命令」的设计灵活性,用最少的扩展「命令」,实现尽可能多的py功能变种。

把复杂度都迁到py后,py这里的设计方式就很重要了,目前是在pyxllib.utools专门设计了类UtoolsBase

  1. 在类初始化,就可以进行所有功能执行前都要做的预处理工作。获得所有变量值
    1. 判断哪些变量是空值
    2. 如果是json,自动先解析出来
    3. 如果没有ClipText,用pyperclip.paste().replace(‘\r\n’, ‘\n’)更鲁棒的形式获得剪切板的内容
    4. 如果有pwd参数,则切换到该工作目录
    5. 增加subinput的简单扩展解析功能
  2. 新增outfmt,可以在py接口简单切换各种返回模式
    1. 具体pandas的DataFrame表达规则,封装_print_df_result函数来实现
    2. 有时候html解析失败(因为windows上默认gbk的原因),会自动切换为browser,使用浏览器打开
  3. 同理,很多类似的操作
    1. 比如要对文件操作,要对图片操作,都可以封装类似的类UtoolsFile、UtoolsImage等
    2. 把某类问题要统一提前预处理的部分,都在类初始化完成
    3. 这样要在py扩展功能就快的多

做到这个程度,已经比原来暴力一个一个功能对接开发快的多了,但还是很麻烦。
pyxllib有很多精细功能操作,如果每次稍微一点变型都要重新增加一个接口很麻烦。
为此,我们做一个更抽象高级的封装,下面以我做的文件批量处理功能为例讲解。

3.2 文件批处理

image.png

配置:

[
    {
        "type": "files",
        "label": "文件/codefile"
    },
    {
        "type": "window",
        "label": "目录/codefile",
        "match": {
            "app": "explorer.exe"
        }
    }
]

脚本:

from pyxllib.utools import UtoolsFile

cmds = dict()
# 通用属性
cmds['isWin'] = {{isWin}}  # 是否Window系统, 返回1或0
cmds['LocalId'] = '{{LocalId}}'  # 本机唯一ID
cmds['subinput'] = r'''{{subinput}}'''  # 子输入框的文本
cmds['type'] = '{{type}}'  # 专业模式的type
cmds['payload'] = r'''{{payload}}'''  # 专业模式的payload, JSON格式字符串

# 窗口可用
cmds['pwd'] = '{{pwd}}'  # 文件管理器当前目录
cmds['WindowInfo'] = r'''{{WindowInfo}}'''  # 当前窗口信息, JSON格式字符串

# 文件可用
cmds['MatchedFiles'] = r'''{{MatchedFiles}}'''  # 匹配的文件,JSON格式字符串

UtoolsFile(cmds).codefile()

3.3 paths/files/dirs

只要3.2里那样配置一个这样的「命令」,以后就可以做很多自定义灵活的文件操作了。
这跟我的codefile功能接口实现方式有关: pyxllib v0.0.71 utools
这里并没有设计任何具体的功能。

而是预制好的一些变量。

比如 paths,这个变量以列表list的形式,存储了当前目录、或者选中的所有对象,以 pathlib.Path 类存储。
启动功能后,可以在subinput窗口写python代码执行!

比如我在一个目录下打开”目录/codefile”功能,然后输出这里文件和目录总数,一共是41个:
image.png
再比如我输出每个路径:
image.png

paths同时存储了文件和目录,也可以用只存储了文件的files,或者只存储了目录的dirs。
files里每个对象是pyxllib的File类,dirs里每个对象是pyxllib的Dir类。

3.4 rpaths/rfiles/rdirs

前面的文件只存储了当前目录下、或者选中的文件。
使用带前缀r的版本,可以递归获得所有子文件。
比如rpaths就是递归所有目录,取出所有路径,rfiles就是递归获得当前以及所有子文件。

3.5 path/file/dir、rpath/rfile/rdir

有时候需要循环处理选中的每个文件,可以不用手动操作files,直接用file变量即可,
codefile功能接口会自动去迭代运行,比如刚才输出所有paths的版本,也可以写成这样:
image.png

3.6 可以扩展更多预制 变量

比如还可以修改源码,添加imfile、rimfile、imfiles、rimfiles,只获取图片文件,然后处理。

3.7 导入自己的包扩展更多功能

可以在3.2的脚本里导入自己的更多高级工具包,然后在subinput可以自由组合,进行很多高级的文件操作。
比如from pyxllib.cv import PilImg,pyxllib库的一个图像处理类。

然后这里初始化一个图像后,执行reduce_filesize()对图片进行压缩,然后再write保存回原始路径。
image.png
因为现在手机拍的很多照片都特别大,没有压缩过,其实只要PIL打开然后重新保存,基本都能从12M降低到2M。
如果想自己精细控制图片大小上限,可以 PilImg(imfile).reduce_filesize(300*1024).write(imfile),
reduce_filesize有参数,这样写可以将图片压缩到300kb以内。

相比传统的在pycharm里写代码运行,这个好处是可以随时调出来取用,甚至可以手动选择目录下若干图片单独操作。

4 「快捷命令」改进建议

utools - 图13
左边菜单栏不能拉宽,一些名称较长的项目就看不全了

(不紧急)支持获取完整字段值的json数据

MatchedFiles 获取的内容有 bug

subinput可以填充默认值?

直接运行,而不是还要再按一下回车?