Author: 颖奇L’Amore
Blog: www.gem-love.com
MacOS高效使用文章合集:here
在前篇中我们介绍了Alfred的内置feature,这篇文章介绍Alfred的终极技能:Workflow
注意,未激活的Alfred不支持workflow功能,所以你需要先购买powerpack激活码
workflow是由用户开发的工作流程,只要你懂得任何一门编程语言基本上都可以来编写自己的workflow,可以通过以下几个渠道获取别人分享的workflow:
其中8是论坛,大家可以自由分享自己写的workflow;其他是精选workflow合集,上面第3个里有相应的效果图,可以先去点进去看一下workflow的使用效果,然后下载自己需要的workflow
有时候我们发现导入了别人的workflow,然后自己用不了,这时候就需要调试了 这里我以我自己的环境为例,实际操作一下debug and fix的流程
下载:https://github.com/willfarrell/alfred-encode-decode-workflow 这是它的使用效果:
但是我却用不了:
此时我们进入该workflow,点击右上角的🕷打开debug页面,可以发现错误原因是/bin/bash: php: command not found
那么我们双击encode和decode的script filter打开,发现它的代码实际上是直接用bash去执行了php
这可能是PATH中找不到php的路径,所以我们直接将其改为自己的php的绝对路径
改完之后就能用了:
下载:https://github.com/yannickglt/alfred-devdocs 这是它的使用效果:
同样我本地用不了:
报错显示找不到所使用的的编程语言,进入workflow双击打开,可以发现它使用的是/usr/bin/php而我们的php不在这个目录
首先考虑用软链接把php链接过来,但奈何/usr/bin目录在当前macos版本已经不可写了
这时候只能用bash去调用php了
首先我们先看一个run script,是不用传参的
使用的语言改成/bin/bash,同时代码改成:
/Applications/MxSrvs/bin/php/bin/php -r <span class="hljs-string">'$query = "nuke";require_once("scripts/conf.php");'</span>
然后我们看node这个script filter,原本使用的/usr/bin/php时的代码如下:
$query = "{query}";
$documentation = 'node';
require_once("scripts/devdocs.php");
这个是要接收参数的,有点麻烦,我们先右键open in finder进入该workflow的目录
新建一个node.php,代码写成:
<span class="hljs-meta"><?php</span>
<span class="hljs-variable">$query</span> = <span class="hljs-variable">$argv</span>[<span class="hljs-number">1</span>];
<span class="hljs-variable">$documentation</span> = <span class="hljs-string">'node'</span>;
<span class="hljs-keyword">require_once</span>(<span class="hljs-string">"scripts/devdocs.php"</span>);
然后使用的语言改成/bin/bash,同时代码改成:
query=<span class="hljs-string">"{query}"</span>
/Applications/MxSrvs/bin/php/bin/php node.php <span class="hljs-variable">$query</span>
此时就解决了/usr/bin/php未安装的问题,尽管此时还不能用,debug显示报错为网页访问不了(但这不是我们workflow的问题了,是对方的文档请求不了了,node命令是直接去这个远程文档去搜索的):
Warning: file_get_contents(http://maxcdn-docs.devdocs.io/node/index.json): failed to open stream: php_network_getaddresses: getaddrinfo failed: nodename nor servname provided, or not known in /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/devdocs.php on line 73
同样的方法把其他的script filter给处理了,依然不能用,看debug的php报错信息:
Deprecated: Array and string offset access syntax with curly braces is deprecated in /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/rodneyrehm/plist/classes/CFPropertyList/CFBinaryPropertyList.php on line 787
Call Stack:
0.0002 395432 1. {main}() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/cdocadd.php:0
0.0008 443448 2. require_once('/Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php') /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/cdocadd.php:4
0.0017 549832 3. CFPropertyList\DevDocsConf->__construct() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:253
0.1059 550896 4. CFPropertyList\DevDocsConf->openPlist() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:40
0.1059 550896 5. spl_autoload_call() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:51
0.1059 550952 6. Composer\Autoload\ClassLoader->loadClass() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:51
0.1059 551208 7. Composer\Autoload\includeFile() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/composer/ClassLoader.php:274
0.1063 625656 8. include('/Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/rodneyrehm/plist/classes/CFPropertyList/CFPropertyList.php') /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/composer/ClassLoader.php:382
Deprecated: Array and string offset access syntax with curly braces is deprecated in /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/rodneyrehm/plist/classes/CFPropertyList/CFBinaryPropertyList.php on line 810
Call Stack:
0.0002 395432 1. {main}() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/cdocadd.php:0
0.0008 443448 2. require_once('/Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php') /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/cdocadd.php:4
0.0017 549832 3. CFPropertyList\DevDocsConf->__construct() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:253
0.1059 550896 4. CFPropertyList\DevDocsConf->openPlist() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:40
0.1059 550896 5. spl_autoload_call() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:51
0.1059 550952 6. Composer\Autoload\ClassLoader->loadClass() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:51
0.1059 551208 7. Composer\Autoload\includeFile() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/composer/ClassLoader.php:274
0.1063 625656 8. include('/Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/rodneyrehm/plist/classes/CFPropertyList/CFPropertyList.php') /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/vendor/composer/ClassLoader.php:382
Warning: Invalid argument supplied for foreach() in /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php on line 107
Call Stack:
0.0002 395432 1. {main}() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/cdocadd.php:0
0.0008 443448 2. require_once('/Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php') /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/cdocadd.php:4
0.0017 549832 3. CFPropertyList\DevDocsConf->__construct() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:253
0.1118 916104 4. CFPropertyList\DevDocsConf->setDocumentations() /Users/amortang/Library/Application Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.9CE45714-7860-4A74-B002-78AA58F3BC8E/scripts/conf.php:41
这是因为7.4的php的数组不支持用大括号寻址了
根据php的报错修改一下源码的对应源码,可以用了:
我们右键可以发现,Alfred支持的东西太多了,比如打开文件、弹个通知、打开浏览器等等
这里面有一些东西是用来控制执行流程之类的东西的,但是最核心的东西其实还是运行脚本,脚本里写的才是我们最核心的操作,你会发现有两种脚本运行:Run Script和Script Filter
简单来讲,想让一个脚本后台自己的去跑一个脚本,没有反馈,这种用run script,例如:
如果想通过一个脚本进行相应操作,不用回车,直接得到单个或多个结果,则需要Script Filter,例如:
我这个电脑的AntSword有个bug,没法退出,每次都得kill进程才能退出,所以我们就以这个为需求来写一个kill蚁剑进程的东西 新建workflow选空白即可,他也给我们提供了一些流程模板
对workflow写一些基本信息
添加一个keywords,用klat
来触发,并选择不需要参数:
后面跟上一个Run Script,不用输入信息,执行固定的语句,语言选择/bin/bash
ps -ef | grep AntSword | grep -v grep | awk '{print $2}' | xargs kill -9
效果如下(蚁剑被kill掉退出了):
当然这个workflow就比较简单(但是也达到了提升工作效率的目的,这就是Alfred的初衷),其他复杂的功能大家自行探索吧,方法就是这样的。
这里我们就来简单写一个货币转换的workflow吧,可以在我的github下载这个workflow
使用方法可以看github的说明,转100美元到欧元效果如下:
整个项目就一个script filter,将用户输入传给了run.py这个python文件:
在看具体文件代码之前,我们需要先来看Alfred的官方文档
这个文档告诉我们,想要使用script filter让结果显示出来,脚本的输出是有固定的json格式的,例如:
{<span class="hljs-attr">"items"</span>: [
{
<span class="hljs-attr">"uid"</span>: <span class="hljs-string">"desktop"</span>,
<span class="hljs-attr">"type"</span>: <span class="hljs-string">"file"</span>,
<span class="hljs-attr">"title"</span>: <span class="hljs-string">"Desktop"</span>,
<span class="hljs-attr">"subtitle"</span>: <span class="hljs-string">"~/Desktop"</span>,
<span class="hljs-attr">"arg"</span>: <span class="hljs-string">"~/Desktop"</span>,
<span class="hljs-attr">"autocomplete"</span>: <span class="hljs-string">"Desktop"</span>,
<span class="hljs-attr">"icon"</span>: {
<span class="hljs-attr">"type"</span>: <span class="hljs-string">"fileicon"</span>,
<span class="hljs-attr">"path"</span>: <span class="hljs-string">"~/Desktop"</span>
}
}
]}
items
是一个数组,里面的每一个元素是一个对象,一个对象就是一个输出结果,具体的参数的意思大家把上面文档读一读就明白了。
接着我们就来写转换货币的具体代码,主要的思路就是获取用户的输入,然后判断是否合法,如果符合要求,就去https://api.getgeoapi.com/v2/currency/convert
这个货币兑换的api进行货币转换。最后的输出一定是要符合它要求的json格式,所以使用了json.dumps
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">import</span> warnings
<span class="hljs-keyword">import</span> json
warnings.filterwarnings(<span class="hljs-string">'ignore'</span>)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">mysplit</span>(<span class="hljs-params">s</span>):</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> re.match(<span class="hljs-string">r"^[\d.]+[a-zA-Z]+$"</span>, s):
sys.exit(<span class="hljs-number">0</span>)
cnt = <span class="hljs-number">0</span>
<span class="hljs-keyword">while</span> cnt < <span class="hljs-built_in">len</span>(s):
<span class="hljs-keyword">if</span> s[cnt].isalpha():
<span class="hljs-keyword">break</span>
cnt += <span class="hljs-number">1</span>
<span class="hljs-keyword">return</span> [s[:cnt], s[cnt:]]
<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(sys.argv) < <span class="hljs-number">2</span> <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> re.match(<span class="hljs-string">r"^[\d.]+[a-zA-Z]+\ [a-zA-Z]+$"</span>, sys.argv[<span class="hljs-number">1</span>]):
sys.exit(<span class="hljs-number">0</span>)
api = os.getenv(<span class="hljs-string">'api'</span>)
amonut_from = sys.argv[<span class="hljs-number">1</span>].split(<span class="hljs-string">' '</span>)[<span class="hljs-number">0</span>]
to = sys.argv[<span class="hljs-number">1</span>].split(<span class="hljs-string">' '</span>)[<span class="hljs-number">1</span>]
amonut_from_split = mysplit(amonut_from)
baseurl = <span class="hljs-string">"https://api.getgeoapi.com/v2/currency/convert"</span>
url = <span class="hljs-string">"%s?api_key=%s&from=%s&to=%s&amount=%s&format=json"</span> % (baseurl, api, amonut_from_split[<span class="hljs-number">1</span>], to, amonut_from_split[<span class="hljs-number">0</span>])
r = requests.get(url)
res = r.json()
<span class="hljs-keyword">if</span> res[<span class="hljs-string">'status'</span>] != <span class="hljs-string">'success'</span> :
<span class="hljs-built_in">print</span>(json.dumps({<span class="hljs-string">"items"</span>:[{<span class="hljs-string">"title"</span>: <span class="hljs-string">"Error"</span>, <span class="hljs-string">"subtitle"</span> : <span class="hljs-string">"something wrong"</span>, <span class="hljs-string">"arg"</span> : <span class="hljs-string">"please check your input"</span>}]}))
sys.exit(<span class="hljs-number">0</span>)
result = {}
<span class="hljs-keyword">try</span>:
result = res[<span class="hljs-string">'rates'</span>][<span class="hljs-built_in">list</span>(res[<span class="hljs-string">'rates'</span>].keys())[<span class="hljs-number">0</span>]]
<span class="hljs-keyword">except</span>:
<span class="hljs-built_in">print</span>(json.dumps({<span class="hljs-string">"items"</span>:[{<span class="hljs-string">"title"</span>: <span class="hljs-string">"Error"</span>, <span class="hljs-string">"subtitle"</span> : <span class="hljs-string">"something wrong"</span>, <span class="hljs-string">"arg"</span> : <span class="hljs-string">"please check your input"</span>}]}))
sys.exit(<span class="hljs-number">0</span>)
items = {<span class="hljs-string">"items"</span>: [
{
<span class="hljs-string">"subtitle"</span>: <span class="hljs-string">"currency name"</span>,
<span class="hljs-string">"title"</span>: result[<span class="hljs-string">'currency_name'</span>],
<span class="hljs-string">"arg"</span>: result[<span class="hljs-string">'currency_name'</span>]
},
{
<span class="hljs-string">"subtitle"</span>: <span class="hljs-string">"rate"</span>,
<span class="hljs-string">"title"</span>: result[<span class="hljs-string">'rate'</span>],
<span class="hljs-string">"arg"</span>: result[<span class="hljs-string">'rate'</span>]
},
{
<span class="hljs-string">"subtitle"</span>: <span class="hljs-string">"Total value of currency after conversion"</span>,
<span class="hljs-string">"title"</span>: result[<span class="hljs-string">'rate_for_amount'</span>],
<span class="hljs-string">"arg"</span>: result[<span class="hljs-string">'rate_for_amount'</span>]
}]
}
<span class="hljs-built_in">print</span>(json.dumps(items))
但是这里面我们使用了一个api接口,这个api接口需要有API Key才可以,我们不能把这个API Key写死在代码里,因为我们希望能让用户自己去注册去申请它自己的API Key,而又不用编写源代码文件就可以把API Key导入进去,这时我们可以设置Alfred当前workflow的环境变量。点击这个[x]
左边是项目的描述,右边就是环境变量的键和值,勾上Do not export的话导出时就不会把值给导出了
这样在代码里就可以通过获取环境变量的方法os.getenv('api')
来取到了。
在share中可以导出workflow,相当于生成release版本,这里可以对项目信息进行一些配置之类的,然后就可以分享给你的小伙伴了,或者分享到Alfred论坛