问题

在我兴致勃勃写了一个 cli 工具(也就是 TyStrings )的时候, 希望在 README 中添加一个 gif 来展示它酷炫的效果.

通常, 我们会这么做:

这相当麻烦. 人为的演示很容易出错, 总要反反复复录制, 后期编辑也非常麻烦.

如果后续维护时, 添加了新的命令, 抑或是仅仅修改了输出, 那么就又要录一次.

这显然很槽糕.

自动化CLI演示的解决方案

我列出了我的自动化需求:

1. 通过脚本执行, 从 录制 到 导出 gif.

2. 脚本演示CLI, 无需人工干预.

我寻找了很久, 可以并没有找到对口的解决方案.

摸索了很久, 最后结合 ttygif + AppleScript + iTerm 2 来做这件事.

ttygif

ttyrec 是录制 tty 的命令行工具

sugyan/ttygif 基于 ttyrec, 但它将 ttyrec 的输出转换成 gif.

所以, 我们使用 ttygif 就好了

AppleScript

Introduction to AppleScript Language Guide

苹果开发的一个脚本语言. 客观来说, 比较偏门. 但是并非找不到它的身影.

因为我想, 你在 macOS 下见过它:

Automator.app

Automator 中, 你可以通过 AppleScript 来编写自动化脚本.

Automator 就是专门为流程自动化而生. 在 Automator 中可以方便的操作各种应用.

在macOS下, 开发者可以使用 XCode 为 macOS 软件添加Automator Action, 从而为应用程序提供 Automator 的支持.

但当我开始使用 Automator 之后… 发现… 我… 不太会拖拖控件…

所以, 最后选择手写 AppleScript.

iTerm 2

iTerm 2 是 macOS 下的一款终端软件.

之所以选择它是因为, 其添加了对 Automator(AppleScript) 的支持. 我们可以通过它提供的 AppleScript API 来操作终端.

iTerm 2 的 AppleScript 文档在: iTerm 2 documentation-scriptin

CLIDemo.scpt

示例源码

我编写了一些方法, 并写了一个 Demo 放到了 gist 上.

你可以下载下来, 通过 Script Editor.app 对它进行编辑和运行.

Script Editor.app

使用方法

- typingStringWithDelay(content, dy)

模拟打字, 在 iTerm 中输入内容, content为待输入的内容, dy 为每个字的输入间隔时间

- typingString(content)

间隔 0.1秒的模拟打字

- runCommand(command)

执行某个命令

- clear()

清空 iTerm, 即执行 clear 命令

- isAppRunning(appName)

检查某个App是否在运行

- setWindowTitle(title)

设置 iTerm 窗口 title

- activateiTerms()

创建/激活 iTerm 窗口

- getCurrentPath()

获取当前脚本所在路径

只要将该部分代码替换成自己的就行.

# — — — — Replace Me — — — -# …替换部分代码# — — — — Replace Me End — — — -

最终效果

录制完之后, 最后的结果如下图所示:

tty-gif-automation demo

在 TyStrings 上的应用

上面提到, 这个需求实际上源于我开发 TyStrings 的时候.

这是 TyStrings 对应的生成演示图的 AppleScript:

这里我只摘了 Replace Me 对应的代码, 剩余的代码与上面重复, 就不再贴出来了.

完整的可以查看 https://github.com/luckytianyiyan/TyStrings/blob/master/script/tystrings.scpt

set currentPath to getCurrentPath() as aliasactivateiTerms()setWindowTitle(“TyStrings Demo”)# cd to repo root pathrunCommand(“cd “ & (quoted form of POSIX path of currentPath) & “../”)runCommand(“source venv/bin/activate”)runCommand(“cd tests/example/”)clear()runCommand(“ttyrec ../../resource/recording”)delay 0.5runTyStringsSubCommand(“generate”, {“$(find . -name \\*.m)”, “-o”, “en.lproj zh-Hans.lproj”})delay 2clear()runTyStringsSubCommand(“translate”, {“strings/base_translator.strings”, “zh-Hans.lproj/base_translator.strings”, “ — dst-lang”, “zh”, “ — src-lang”, “en”})delay 2clear()runTyStringsSubCommand(“diff”, {“strings/diff1.strings”, “strings/diff2.strings”})delay 2clear()runTyStringsSubCommand(“lint”, {“strings/lint.strings”})delay 2runCommand(“exit”)delay 0.5runCommand(“cd “ & (quoted form of POSIX path of currentPath) & “../”)runCommand(“deactivate”)runCommand(“ttygif resource/recording -f”)runCommand(“mv tty.gif resource/tystrings.gif”)# cleanrunCommand(“rm -rf tests/example/en.lproj”)runCommand(“rm -rf tests/example/zh-Hans.lproj”)# — — — — TyStrings Helper — — — -on runTyStringsSubCommand(subcommand, args)typingString(“tys”)typingStringWithDelay(“trings “, 0)typingString(subcommand & “ “)repeat with i from 1 to count of argstypingStringWithDelay(item i of args & “ “, 0.02)end repeat# typing ‘\n’typingString(““)end runTyStringsSubCommand

最终 TyStrings 的展示结果

https://raw.githubusercontent.com/luckytianyiyan/TyStrings/master/resource/tystrings.gif

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade