7fd0d18633012450f5edb39d75d6de2c
Taskbook 代码阅读:Star 3k 的 Todo CLI 工具

介绍

taskbook 是 GitHub 上最近比较流行的一个开源项目,目前 star 数目接近 3k。它是一个命令行 Todo 工具,使用前端技术编写。

作者将其概括为供命令行爱好者使用的 Task、Board、Notes 工具。其中,Task、Board、Notes 是这个工具中的 3 个核心概念,在后文中我们将详细学习。

详细来说,这小工具具备如下特性:

  • 管理 Task、Board、Notes
  • Board 视图和时间线视图
  • 优先级机制、标星机制
  • 搜索过滤项目
  • 归档、恢复项目
  • 轻量级、速度快
  • 原子数据写入
  • 自定义存储位置
  • 进度条视图
  • 易用
  • 升级提醒
  • 自定义配置

看完这些,我想到一些比较感兴趣的点:

  1. 数据结构是如何定义的?
  2. 作者如何处理数据间的依赖关系?
  3. 数据是如何保存的?
  4. 美观的命令行界面是如何实现的?

这个程序总体上比较复杂度不高,并且结构清晰、业务拆分合理,对于初中级开发者是一个非常好的学习资源。

Clone 下代码,yarn 安装好依赖,代码阅读正式开始~

依赖分析

我们先看看工具的依赖,都是很优秀的库:

chalk -- Terminal string styling done right

taskbook 在命令行上五彩斑斓的的显示效果就是通过 chalk 实现的。

meow -- CLI app helper

taskbook 特性中易用的命令行接口,很大一部分要归功于 meow

meow 能够帮助你快捷地生成规范、好用的命令行接口。

signale -- Hackable console logger

signale 是一个可扩展的日志记录器,有高质量的中文文档

打出来的 log 十分美观:

update-notifier - Update notifications for your CLI app

update-notifier 是一个针对命令行工具的升级提示器。

xo - JavaScript happiness style linter

xo 是一个对 ESLint 的封装,强化了代码风格检查的严格性和可读性。

小结

上面这几个依赖个个都是精品,立刻收入囊中。以后开发工具使用它们,立刻上升一个台阶。

Task、Board、Notes

这一节开始我们就进入 taskbook 的内部实现。taskbook 是一个数据驱动的软件,因此第一步,就是要建立它的数据模型。

从代码的 Model 定义得到如下数据关系:

其中:

  • Item 是 Model 基类,包括一些通用属性
  • Task 是 Todo 中的任务,继承 Item
  • Note 是笔记,同样继承 Item
  • Task 和 Note 通过 _isTask 进行区分,不是 Task 就是 Note

Board 是看板的意思,Task 和 Note 可以属于一个看板,也可以属于多个看板。Board 是没有实体的,它隐藏在 Item 的 boards 属性中,是一个字符串数组。

我们可以通过下图再总结一下:

以上就是数据模型部分,是不是很简单?很多前后端框架的演示 Demo 都会选择 Todo App,就是因为这种 APP 的数据结构定义清晰,复杂度适中,易于上手。

状态存储

前面定义了数据模型,他们在程序运行的时候、在磁盘中是以什么形式存储的呢?

首先,程序的运行过程如下:

用户输入指令 -> 启动 -> 载入数据 -> 操作 -> 输出 -> 结束 

从中可以看出,数据不会再内存中长期停留,而是一锤子买卖,这就省了很多事情。

taskbook 的数据就是一个 JSON,在内存里跟在磁盘中一个样,其内容为:

{
    "1": {
        "_id": 1,
        "_date": "Thu Aug 02 2018",
        "_timestamp": 1533178370971,
        "description": "任务内容...",
        "isStarred": false,
        "boards": [
            "@work"
        ],
        "_isTask": true,
        "isComplete": true,
        "priority": 1
    },
    "2": {
        "_id": 2,
        "_date": "Thu Aug 02 2018",
        "_timestamp": 1533178512037,
        "description": "任务内容...",
        "isStarred": false,
        "boards": [
            "@work"
        ],
        "_isTask": true,
        "isComplete": true,
        "priority": 1
    },
    ...
}    

读取就是一次性读入内存,之后在内容中对上面这个结构进行操作,操作完再全量地写回文件中。

这种全量的数据写入方式似乎有点暴力,不过好处是简单。很多时候简单就是好事。

下面我们看下读取和写入的方法代码:

```js
get() {
let data = {};

if (fs.existsSync(this._mainStorageFile)) {

top Created with Sketch.