那个极爽的命令行纠错软件 The Fuck 是如何工作的

改好的命令

这里最重要的部分就是匹配规则了,规则是一个特殊模块集,它有两个方法:

  • match(command: Command) → bool – 匹配上规则则返回 True;
  • get_new_command(command: Command) → str|list[str] – 否则返回修改后的命令或命令列表(当有多个可能匹配项)

我想这个应用只是因为它的规则才这么有趣,编写自己的规则也很简单。目前有 75 条可用的规则,大都是有第三方贡献者写的。命令是一个类似命名元组namedtuple这样的数据结构:


  1. Command(script: str, stdout: str, stderr: str)

其中 script 是与 shell 类型无关的错误命令。

处理不同 Shell 类型

在不同的 shell 中,描述 alias 的方式不同、语法不同(比如在 fish 中 && 表示为 and)、历史命令的处理方法也不同,且 shell 还依赖特定的配置文件(.bashre ,.zshrc 等)。为了避免这些麻烦,在程序中有一个 shells 的模块把这些与特定 shell 相关的命令转化为与 sh 兼容的类型,并展开别名和环境变量。 所以我们使用shells.from_shell 方法来获得 Command(前面的章节提到过的)的实例,在 sh 里运行并且获得 stdout 和 stderr。

出错的命令 → from_shell 模块 → 与 shell 类型无关的命令 → (可以)在 sh 内运行 –> Command 实例

对修改好的命令也做了相似地处理,即把与特定 shell 无关的命令通过 shells.to_shell 模块转化为与 shell 相关的命令。

配置

The Fuck 是一个高可配置的应用,用户可以开启或关闭规则、配置 UI、设置规则选项还有进行其他的操作。用户可以通过修改 ~/.thefuck/settring.py 文件以及环境变量来配置应用:

默认配置 → 通过 setting.py 文件更新 → 通过环境变量更新

之前版本中,配置对象以参数的形式传递到所有需要的场合,虽然那样还不错并且能够测试,但存在过多的重复代码。而现在是一个单例(thefuck.conf.settings),类似 Django 中的 django.conf.settings

UI

The Fuck 的 UI 很简单,它允许用户通过(上下)箭头的方式在修正过的命令列表中进行选择,使用 Enter 来确认选择,Ctrl+C 来跳出程序。 美中不足的是在 Python 标准库中没有办法在非 Windows 下不通过 curses 来读取键盘输入,由于别名alias的特性我们又不能在这里使用 curses。但容易写出针对 Windows 的 msvrt.getch:


  1. import tty
  2. import termios
  3. def getch():
  4. fd = sys.stdin.fileno()
  5. old = termios.tcgetattr(fd)
  6. try:
  7. tty.setraw(fd)
  8. ch = sys.stdin.read(1)
  9. if ch == '\x03': # For compatibility with msvcrt.getch
  10. raise KeyboardInterrupt
  11. return ch
  12. finally:
  13. termios.tcsetattr(fd, termios.TCSADRAIN, old)

另外 UI 也需要修复好的程序命令组成的有序列表,且规则匹配耗时应该尽量较短。而加入简单的启发式算法后效果还不错,首先我们按照优先级来匹配规则,第一个返回的修复过的命令是有最大优先级的命令。当用户按下箭头按键时再选择其他的命令。所以在大多数的使用场景中都能很快完成任务。

整体来看

如果从整体来看一下这个应用,会发现它很简单:

那个极爽的命令行纠错软件 The Fuck 是如何工作的

其中 controller(控制器)是当用户使用 The Fuck 来修复错误命令时的程序入口,它初始化设置、准备 shells 的交互环境、从 Corrector (修正器)来获取修正过的命令并在 UI 中选择。Corrector 使用所有可用的规则来匹配当前命令并且返回所有可用的修复过的命令。关于UI、设置和规则就说到这里。

测试

测试是所有软件项目的最重要的部分之一。没有测试,软件可能会由于任一个改变而崩溃。我们使用 pytest 来进行单元测试。由于应用中存在规则,所以需要做很多测试来匹配和确认修正过的命令。所以,参数化的测试用例是很有用的,典型的测试是这样的:


  1. import pytest
  2. from thefuck.rules.cd_mkdir import match, get_new_command
  3. from tests.utils import Command
  4. @pytest.mark.parametrize('command', [
  5. Command(script='cd foo', stderr='cd: foo: No such file or directory'),
  6. Command(script='cd foo/bar/baz',
  7. stderr='cd: foo: No such file or directory'),
  8. Command(script='cd foo/bar/baz', stderr='cd: can\'t cd to foo/bar/baz')])
  9. def test_match(command):
  10. assert match(command)

The Fuck 可以与许多种类的 shell 共同工作,而每个 shell 又需要特定的别名。为了保证所有别名可用,需要用到功能测试,其中用到了我写的 pytest-docker-pexpect 模块,在一个 docker 容器内设置一个场景来测试所有支持的命令。

发布

The Fuck 应用的最麻烦的部分是它的安装,应用通过 pip 来发布,由此产生了一些问题:

  • 有些平台上依赖 python 的头文件(python-dev),所以我们需要告诉用户手动地安装;
  • pip 不支持安装后自动完成一些自定义操作,所以用户需要手动配置一个别名;
  • 有些用户使用不支持的 python 版本,应用只支持 2.7 或者 3.3+ 的版本;
  • 有些老版本的 pip 根本就不安装依赖项;
  • 有些版本的 pip 忽视 Python 版本的依赖关系,所以需要为早于 3.4 的版本安装 pathlib;
  • 有趣的是有人对这个名字感到很愤怒并且尝试从 pypi 中移除这个包;

原文发布时间为:2017-05-05

本文来自云栖社区合作伙伴“Linux中国”

上一篇:OpenResty 下载与安装


下一篇:使用 Github Pages 发布你的项目文档