最近心血来潮,想做一个 node 工具,作为一个 node.js 新学者,通过边学边翻文档,做了这个工具 todo-kcvo
功能
- 可以列出所有的 TODO
- 可以新增 TODO
- 可以编辑 TODO
- 可以删除 TODO
- 可以标记 TOOD 状态为 已完成/未完成
简单来说就是增删改查
使用的库
- commander - 文档
- inquirer - 文档
如何使用命令行
新建 cli.js
const { program } = require('commander');
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size', 'you')
.option('-p, --pizza-type <type>', 'flavour of pizza');
program.parse(process.argv);
if (program.debug) console.log(program.opts());
if (program.small) console.log('- small pizza size');
if (program.pizzaType) console.log(`- ${program.pizzaType}`);
// 这是一个官方文档的例
在命令行中输入 node cli -h
会的出现这个,代表拥有这些选项
新建命令
由于是 TODO 工具,所以我们需要 add 命令
// 插入以下代码
program
.command('add')
.description('新增一个任务')
.action((...args) => {
const works = args[1] && args[1].join(' ')
console.log(works)
});
在命令行中输入 node cli add todo 1
新增 index.js 用于写 API 函数
// index.js
module.exports.add = (task) => {
console.log(`add ${task}`)
}
// cli.js
const { program } = require('commander');
const api = require('./index.js')
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size', 'you')
.option('-p, --pizza-type <type>', 'flavour of pizza');
program
.command('add')
.description('新增一个任务')
.action((...args) => {
const works = args[1] && args[1].join(' ')
api.add(works)
});
program.parse(process.argv);
在命令行运行 node cli add todo 1
得到和上次同样的结果
本地数据库
todo 的数据库可以选用home目录
node.js 如果找到 home 目录呢?
Google -- node find hone path
我找的了一条命令
const homedir = require('os').homedir();
// 打印出 homedir 结果是 C:UsersAdministrator
这样好像没毛病,但是如果有的用户,把 home 目录改到其他盘呢?
由于这个信息是存储在环境变量里的
于是我试着
Google -- node.js get home variable
找到了一条命令
const HOME = process.env.HOME;
// 打印出 homedir 结果是 D:Administrator
// 由于我把 home 目录移到 D 盘了
Finally
然而对于没有改 home 目录的用户,process.env.HOME 是空的,所有最终的代码是
const home = process.env.HOME || require('os').homedir();
读写文件
上面说了如何找到路径,那接下来当然是读写了
首先看一下 fs.readFile 文档
第一个参数 path ,那就是
const home = process.env.HOME || require('os').homedir();
path = home + '/.todo'
// 好像不想,在 window 里面,路径是 这样的
path = home = '.todo'
// 也不行,在 mac 系统就不支持
// 那怎么办?
使用 path
没错 path 就是用了拼路径的
于是我写下了这样的代码
const home = process.env.HOME || require('os').homedir();
const fs = require('fs')
const p = require('path')
const dbPath = p.join(home, '.todo')
module.exports.add = (task) => {
fs.readFile(dbPath, (err, data) => {
if (err) throw err;
console.log(data.toString());
});
console.log(`add ${task}`)
}
然后在命令行中运行 node cli add
却遇到了报错
找不到 .todo 文件
再看一下文档,fs.readFile() ,可接受 options 参数,其中 {flag:"a+"}, 就是如文件不存在就创建文件
经过改写
fs.readFile(dbPath,{flag: 'a+'}, (readErr, data) => {
if (readErr) throw readErr;
console.log(data.toString())
});
运行 node cli add
不会报错
写入
读文件之后当然是要写入了,数据存储采用 数组,代码如下
const home = process.env.HOME || require('os').homedir();
const fs = require('fs')
const p = require('path')
const dbPath = p.join(home, '.todo')
module.exports.add = (taskName) => {
fs.readFile(dbPath,{flag: 'a+'}, (readErr, data) => {
if (readErr) throw readErr;
let list
try {
list = JSON.parse(data.toString())
} catch {
list = []
}
console.log(list, 'read')
const task = {
title: taskName,
done: false
}
list.push(task)
const string = JSON.stringify(list)
fs.writeFile(dbPath, string, (writeErr) => {
if (writeErr) throw writeErr;
console.log(list, 'write');
});
});
}
可写入文件了
封装优化
最终我们希望优化成这样
// 读取之前的任务
const list = db.read()
// 往里面添加任务
list.push({title: taskName, done: false})
// 存储任务
db.write()
首先我们创建个 db.js 用于写操作数据库的接口
写入 read 和 write 函数
const home = process.env.HOME || require('os').homedir();
const fs = require('fs')
const p = require('path')
const dbPath = p.join(home, '.todo')
const db = {
read(path = dbPath) {
// 由于是 readFile 是异步的,所以需要用 promise 返回数据
return new Promise((resolve, reject) => {
fs.readFile(path,{flag: 'a+'}, (error, data) => {
let list
if (error) return reject(error)
try {
list = JSON.parse(data.toString())
} catch {
list = []
}
resolve(list)
});
})
},
write(list, path = dbPath) {
return new Promise((resolve, reject) => {
fs.writeFile(path, JSON.stringify(list), (error) => {
if (error) return reject(error);
resolve(true)
console.log(list, 'write');
});
})
}
}
module.exports = db
index.js
const db = require('./db.js')
module.exports.add = async (taskName) => {
// 读取之前的任务
const list = await db.read()
// 往里面添加任务
list.push({title: taskName, done: false})
// 存储任务
await db.write(list)
}
增加 clear 功能
首先再 cli 中增加 clear 命令
program
.command('clear')
.description('清空任务')
.action(() => {
api.clear()
console.log(`清空任务`);
});
// 调用了 api 的 clear 函数
index.js
module.exports.clear = async () => {
await db.write([])
}
所谓的清空就是写入空数组
展示任务
当用户不填参数时,应该展示所有任务
如何识别未传参数
console.log(process.argv)
通过打印 process.argv 我们可以发现前两个参数是默认
当我们输入参数时
node cli abc
会出现在第三个参数
所以我们可以这样判断
if (process.argv.length === 2) {
console.log('用户直接运行 node cli 未传参数')
api.showAllTask()
}
展示任务
module.exports.showAllTask = async () => {
const list = await db.read()
list.forEach((task, index) => {
console.log(`${task.done ? '[*]' : '[_]'} ${index + 1} - ${task.title}`)
})
}
如何实现修改
这时候就需要用到 inquirer
基础操作
inquirer
.prompt([
/* Pass your questions in here */
{
type: 'list',
name: '任务',
message: '请选择你想操作的任务',
choices: ["1", "2", "3"]
}
])
.then(answers => {
// Use user feedback for... whatever!!
console.log(answers.index)
})
.catch(error => console.log(error))
补全代码
module.exports.showAllTask = async () => {
const list = await db.read()
inquirer
.prompt([
/* Pass your questions in here */
{
type: 'list',
name: 'index',
message: '选择你想操作的任务',
choices: [...list.map((task, index) => {
return {name: `${task.done ? '[*]' : '[_]'} ${index + 1} - ${task.title}`, value: index.toString()}
}),{name: '+ 创建任务', value: '-2'}, {name: '退出', value: '-1'}]
}
])
.then(answers => {
console.log(answers.index)
const index = parseInt(answers.index)
if (index >= 0) {
inquirer
.prompt([
{
type: 'list',
name: 'value',
message: '选择你需要的操作',
choices: [
{name: "标记已完成",value:"done"},
{name: "标记未完成", value: 'undone'},
{name: "删除任务", value: 'delete'},
{name: "修改任务名", value: 'edit'},
{name: "退出", value: 'quit'},
]
}
])
.then(answers1 => {
switch (answers1.value) {
case 'done':
list[index].done = true
db.write(list)
break
case 'undone':
list[index].done = false
db.write(list)
break
case 'delete':
list.split(index, 1)
db.write(list)
break
case 'edit':
inquirer
.prompt([
{
type: 'input',
name: 'value',
message: '新名字:',
}
])
.then(answers2 => {
list[index].title = answers2.value
db.write(list)
})
break
}
})
} else if (index === -2) {
inquirer
.prompt([
{
type: 'input',
name: 'value',
message: '新任务:',
}
])
.then(answers3 => {
list.push({title: answers3.value, done: false})
db.write(list)
})
}
})
.catch(error => {
console.log(error)
});
}
发布
目前主要功能已经完成了,是时候发布了
加入 node shebang
在 cli.js 文件第一行加入
#!/usr/bin/env node
package.json
{
"name": "todo-kcvo", // 自定义名字,不可以和我的重复
"version": "0.0.1",
"bin": {
"t":"cli.js"
},
"files": [
"cli.js",
"db.js",
"index.js"
],
"description": "todo-kcvo make you more efficient",
"main": "cli.js",
"license": "MIT",
"dependencies": {
"commander": "^5.1.0",
"inquirer": "^7.3.0"
}
}
使得 cli.js 变成可执行文件
// 命令行运行
chmod -x cli.js
发布
nrm use npm // npm 源使用官方源
npm adduser // 如果没有 npm 账号,请去官网注册
npm publish
// 如果发布成功,请尝试
npm install -g /* 你的npm包名 */
最后献上完整代码 -- code