Workflow
实现 WorkFlow 的几个基本对象:trigger、input、action、utility、output。Alfred 通过将这几个基本对象连接起来,便能实现各种高级功能。
配置界面
以有道翻译Alfred Workflow为例,分别介绍workflow的五大元素和三个功能。
五大元素
- trigger 和 input 主要用来触发后续操作(通过传递Arg)。其中trigger的触发相当于【打开alfred且输入关键词】这两个步骤。即同时按住control+option+z这一个步骤就相当于完成了【command+space 唤起alfred】+【输入yd】这两个步骤。(可以没有trigger,主要看具体需求)
- action负责处理进一步需求(对传进来的Arg做处理)。(可以没有action,主要看具体需求)
- output 负责把前面节点的结果以一定的形式传递给用户,可以是直接屏显,也可以是另存为文件,播放声音等。(可以没有output,主要看具体需求)
- utility是各种工具节点:如对中间文本进行过滤、替换等。(可以没有utility,主要看具体需求)
- 上述节点可以用「线」链接,来形成直接或间接的条件结果关系。线上的节点可以设置为按键触发。如图中按option键后回车可以播放在线语音
三个功能
环境变量
用来设置workflow运行中所需要的用户配置信息。如有道翻译中需要用户在平台申请key和secret信息供脚本在运行过程中请求对应的翻译API。如果没有配置则workflow运行中会报错。
Dont Export:表示在导出workflow分享给别人时,勾选上的变量的值不会进行导出以保护隐私
导出
调试
主要用于自己开发workflow时进行debug。比如根据报错信息或者自己打印出的关键步骤信息定位问题所在。
编写一个自己的workflow
Step1 配置workflow面板
- 在workflow面板下方点击+并选择Blank Workflow
- 填写相关信息
- 搞一个Script Filter,填写关键词(用来触发脚本),并明确在输入命令后需要执行的脚本指令(Script文本区域)。这里icon、cpu和expire是定义的环境变量,$1是需要输入的内容
- 根据需要加入元素,并用线连接起来
Step2 写代码
使用到的Go框架:github.com/deanishe/aw…
- go get -u github.com/deanishe/awgo
- 搭建框架代码
package main
// Package is called aw
import "github.com/deanishe/awgo"
// Workflow is the main API
var wf *aw.Workflow
func init() {
// Create a new Workflow using default settings.
// Critical settings are provided by Alfred via environment variables,
// so this *will* die in flames if not run in an Alfred-like environment.
wf = aw.New()
}
func main() {
// Wrap your entry point with Run() to catch and log panics and
// show an error in Alfred instead of silently dying
wf.Run(run)
}
// Your workflow starts here
func run() {
// Add a "Script Filter" result
wf.NewItem("First result!")
// Send results to Alfred
wf.SendFeedback()
}
- 填充逻辑
package main
import (
"encoding/json"
"fmt"
"github.com/deanishe/awgo"
"github.com/shirou/gopsutil/process"
"log"
"os"
"regexp"
"strconv"
"strings"
"time"
)
var wf *aw.Workflow
const (
DEFAULT_EXPIRE_SECONDS = 60
)
func init() {
// Create a new Workflow using default settings.
// Critical settings are provided by Alfred via environment variables,
// so this *will* die in flames if not run in an Alfred-like environment.
wf = aw.New()
}
func main() {
// Wrap your entry point with Run() to catch and log panics and
// show an error in Alfred instead of silently dying
wf.Run(run)
}
// Your workflow starts here
func run() {
log.Println("cmd = ", os.Args)
processMap, err := GetProcessMapFromCacheOrRequest()
if err != nil {
wf.NewItem("出错了,请重试")
wf.SendFeedback()
return
}
query := os.Args[len(os.Args)-1]
limit := 0
for name, process := range processMap {
if limit > 50 {
break
}
pName := strings.Split(name, "::::")[0]
if strings.Contains(strings.ToUpper(pName), strings.ToUpper(query)) {
item := wf.NewItem(pName).Arg(fmt.Sprintf("%d", process.Pid)).Valid(true)
if isShowCpu() {
item.Subtitle(fmt.Sprintf("kill %s, pid=%d, cpu=%.2f%s", pName, process.Pid, process.CPU, "%"))
} else {
item.Subtitle(fmt.Sprintf("kill %s, pid=%d", pName, process.Pid))
}
if isShowIcon() {
item.Icon(&aw.Icon{
Value: process.IconPath,
Type: aw.IconTypeFileIcon,
})
}
limit += 1
}
}
if limit == 0 {
wf.NewItem("no such process")
}
// Send results to Alfred
wf.SendFeedback()
}
type Process struct {
Pid int32
IconPath string
CPU float64
}
// GetProcessMapFromCacheOrRequest 从缓存中获取进程映射,如果过期则重新请求
func GetProcessMapFromCacheOrRequest() (map[string]*Process, error) {
var processMap map[string]*Process
if wf.Cache.Exists("processMap") && !wf.Cache.Expired("processMap", time.Second*time.Duration(GetExpireSeconds())) {
log.Println("use cache")
bytes, err := wf.Cache.Load("processMap")
if err == nil {
err = json.Unmarshal(bytes, &processMap)
if err == nil {
return processMap, nil
} else {
log.Printf("[GetProcessMapFromCacheOrRequest] load from cache failed, json.Unmarshal err=%s", err.Error())
}
}
}
log.Println("miss cache or occur error")
processMap = ProcessMap()
res, err := json.Marshal(processMap)
if err == nil {
wf.Cache.Store("processMap", res)
} else {
log.Printf("[GetProcessMapFromCacheOrRequest] Store cache failed, err=%s", err.Error())
}
return processMap, nil
}
func ProcessMap() map[string]*Process {
pids, _ := process.Pids()
processMap := make(map[string]*Process, len(pids))
for _, pid := range pids {
pn, err := process.NewProcess(pid)
if err != nil {
continue
}
// 进程名
pName, err := pn.Name()
if err != nil {
continue
}
// 应用图标
IconPath := ""
if isShowIcon() {
cmd, _ := pn.Cmdline()
IconPath = GetIconPath(cmd)
}
// 当前CPU
cpu := 0.0
if isShowCpu() {
cpu, _ = pn.CPUPercent()
}
processMap[fmt.Sprintf("%s::::%d", pName, pid)] = &Process{
Pid: pid,
IconPath: IconPath,
CPU: cpu,
}
}
return processMap
}
// 是否需要显示CPU信息
func isShowCpu() bool {
isShowCpuStr, isExisted := wf.Alfred.Env.Lookup("show_cpu")
return isExisted && isShowCpuStr == "1"
}
// 是否显示应用图标
func isShowIcon() bool {
isShowIconStr, isExisted := wf.Alfred.Env.Lookup("show_icon")
return isExisted && isShowIconStr == "1"
}
// GetExpireSeconds 获取缓存时长(单位:秒),默认60
func GetExpireSeconds() int64 {
expireStr, isExisted := wf.Alfred.Env.Lookup("expire_seconds")
if !isExisted {
return DEFAULT_EXPIRE_SECONDS
}
expireSeconds, err := strconv.ParseInt(expireStr, 10, 64)
if err != nil {
return DEFAULT_EXPIRE_SECONDS
}
return expireSeconds
}
// GetIconPath 获取应用图片路径
func GetIconPath(cmd string) string {
if cmd == "" {
return cmd
}
re, err := regexp.Compile("\.app\/Contents\/.*$")
if err != nil {
return ""
}
return re.ReplaceAllString(cmd, ".app")
}
Step3 build
go build -o workflow
Step4 手动替换
- 找到刚刚build好的workflow
- 找到alfred里面该workflow所在的位置
- 将build好的workflow移动到第二步中所在文件夹即可
效果演示
输入 kill macd可以关闭macdown,动手试试吧~