todo 工具功能介绍:
一、创建 add 和 clear 命令
Commander.js 可以用来帮助我们开发命令行界面,这个 todo 工具用的是 Commander.js v3.0.2。
yarn add commander@3.0.2
使用 CRM 学习法,把代码从文档中抄过来运行、修改,得到如下代码:
// cli.js
const program = require('commander');
const pkg = require('./package.json');
// 添加参数
program
.version(pkg.version)
// 添加命令 add
program
.command('add')
.description('add a task')
.action((...args) => {
const words = args.slice(0, -1).join(' ')
console.log('words:', words)
});
// 添加命令 clear
program
.command('clear')
.description('clear all task')
.action(() => {
console.log('清除成功')
});
program.parse(process.argv);
二、实现 add 功能
1. cli 调用 index 中的 add 方法
// index.js
module.exports.add = (title) => {
console.log(title)
}
// cli.js(部分代码)
const api = require('./index.js');
// 添加命令 add
program
.command('add')
.description('add a task')
.action((...args) => {
const words = args.slice(0, -1).join(' ')
api.add(words)
});
2. 确定数据保存的位置
建议将 todo 数据保存在用户(home)目录中,但是用户也可能把用户目录重置到了其他地方,所以我们还要获取用户设置的 home 变量。
// index.js
const userHomeDir = require('os').homedir();
const home = process.env.home || userHomeDir; // 优先把数据存到用户自己设置的 home 中
module.exports.add = (title) => {
console.log(title)
console.log('userHomeDir:', userHomeDir)
console.log('home:', home)
}
3. 实现 add 功能
// index.js
const userHomeDir = require('os').homedir();
const home = process.env.home || userHomeDir;
const path = require('path')
const dbPath = path.join(home, '.todo')
const fs = require('fs')
module.exports.add = (title) => {
// 读取文件内容,如果文件不存在,就创建 .todo 文件
fs.readFile(dbPath, { flag: 'a+' }, (err, data) => {
if (err) throw err
let list
try {
list = JSON.parse(data.toString())
} catch (err) {
list = []
}
// 往 list 中添加一个任务
list.push({ title, done: false })
const content = JSON.stringify(list) + '\n'
// 把任务写到 .todo 文件中
fs.writeFile(dbPath, content, err => {
if (err) throw err
console.log('创建成功')
})
})
}
4. 优化代码
把对数据的读写操作封装到 db.js 中:
// db.js
const userHomeDir = require('os').homedir();
const home = process.env.home || userHomeDir;
const path = require('path')
const dbPath = path.join(home, '.todo')
const fs = require('fs')
const db = {
read(path = dbPath) {
return new Promise((resolve, reject) => {
fs.readFile(path, { flag: 'a+' }, (err, data) => {
if (err) throw err
let list
try {
list = JSON.parse(data.toString())
} catch (err) {
list = []
}
resolve(list)
})
})
},
write(list, path = dbPath) {
return new Promise((resolve, reject) => {
const content = JSON.stringify(list) + '\n'
fs.writeFile(path, content, err => {
if (err) throw err
resolve('success')
})
})
}
}
module.exports = db
// index.js
const db = require('./db.js')
module.exports.add = async (title) => {
// 读取文件内容,如果文件不存在,就创建 .todo 文件
const list = await db.read()
// 往 list 中添加一个任务
list.push({ title, done: false })
// 把任务写到 .todo 文件中
const result = await db.write(list)
console.log(result)
}
三、实现 clear 功能
1. cli 调用 index 中的 clear 方法
// index.js(部分代码)
module.exports.clear = () => {
console.log('清除成功')
}
// cli.js(部分代码)
const program = require('commander');
const api = require('./index.js');
// 添加命令 clear
program
.command('clear')
.description('clear all task')
.action(() => {
api.clear()
});
2. 实现 clear 功能
// index.js(部分代码)
const db = require('./db.js')
module.exports.clear = async () => {
const result = await db.write([])
console.log(result)
}
四、实现与命令行交互功能
Inquirer.js 可以实现与命令行进行交互,这个 todo 工具使用的是 inquirer v 8.2.4。
yarn add inquirer
1. 展示 todo 列表
// index.js(部分代码)
const db = require('./db.js');
const inquirer = require('inquirer');
module.exports.showAll = async () => {
const list = await db.read()
inquirer.prompt({
type: 'list',
name: 'index',
message: '请选择你想操作的任务',
choices: [
{ name: '退出', value: '-1' },
...list.map((task, index) => {
return { name: `${task.done ? '[√]' : '[_]'} ${index + 1} - ${task.title}`, value: index.toString() }
}),
{ name: '+ 创建任务', value: '-2' }
]
}).then(answer => {
console.log(answer)
});
}
2. 展示操作列表
// index.js(部分代码)
const db = require('./db.js');
const inquirer = require('inquirer');
module.exports.showAll = async () => {
const list = await db.read()
// 展示 todo 列表
inquirer.prompt({
// ...
}).then(answer => {
const index = parseInt(answer.index)
// 选择任务
if (index >= 0) {
// 展示操作列表
inquirer.prompt({
type: 'list',
name: 'action',
message: '请选择操作',
choices: [
{ name: '退出', value: 'quit' },
{ name: '已完成', value: 'markAsDone' },
{ name: '未完成', value: 'markAsUndone' },
{ name: '改标题', value: 'updateTitle' },
{ name: '删除', value: 'remove' },
]
}).then(answer => {
console.log(answer)
})
}
// 创建任务
if (index === -2) {
console.log('创建任务')
}
});
}
3. 执行操作
// index.js(部分代码)
const db = require('./db.js');
const inquirer = require('inquirer');
module.exports.showAll = async () => {
const list = await db.read()
// 展示 todo 列表
inquirer.prompt({
// ...
}).then(answer => {
const index = parseInt(answer.index)
// 选择任务
if (index >= 0) {
// 展示操作列表
inquirer.prompt({
// ...
}).then(answer => {
// 执行操作
switch (answer.action) {
case 'markAsDone':
list[index].done = true
db.write(list)
break
case 'markAsUndone':
list[index].done = false
db.write(list)
break
case 'updateTitle':
inquirer.prompt({
type: 'input',
name: 'title',
message: '新的标题',
default: list[index].title
}).then(answer => {
list[index].title = answer.title
db.write(list)
})
break
case 'remove':
list.splice(index, 1)
db.write(list)
break
}
})
}
// 创建任务
if (index === -2) {
console.log('创建任务')
}
});
}
4. 创建任务
// index.js(部分代码)
const db = require('./db.js');
const inquirer = require('inquirer');
module.exports.showAll = async () => {
const list = await db.read()
// 展示 todo 列表
inquirer.prompt({
// ...
}).then(answer => {
const index = parseInt(answer.index)
// 选择任务
if (index >= 0) {
// 展示操作列表
inquirer.prompt({
// ...
}).then(answer => {
// 执行操作
// ...
})
}
// 创建任务
if (index === -2) {
inquirer.prompt({
type: 'input',
name: 'title',
message: '请输入任务标题'
}).then(answer => {
list.push({ title: answer.title, done: false })
db.write(list)
})
}
});
}
5. 优化代码
// index.js(部分代码)
const db = require('./db.js');
const inquirer = require('inquirer');
module.exports.showAll = async () => {
const list = await db.read()
showTodoList(list).then(answer => {
const index = parseInt(answer.index)
if (index >= 0) {
showActionList().then(answer => {
doAction(list, index, answer.action)
})
}
if (index === -2) {
createTask(list)
}
});
}
// 展示 todo 列表
function showTodoList(list) {
return inquirer.prompt({
type: 'list',
name: 'index',
message: '请选择你想操作的任务',
choices: [
{ name: '退出', value: '-1' },
...list.map((task, index) => {
return { name: `${task.done ? '[√]' : '[_]'} ${index + 1} - ${task.title}`, value: index.toString() }
}),
{ name: '+ 创建任务', value: '-2' }
]
})
}
// 展示操作列表
function showActionList() {
return inquirer.prompt({
type: 'list',
name: 'action',
message: '请选择操作',
choices: [
{ name: '退出', value: 'quit' },
{ name: '已完成', value: 'markAsDone' },
{ name: '未完成', value: 'markAsUndone' },
{ name: '改标题', value: 'updateTitle' },
{ name: '删除', value: 'remove' },
]
})
}
// 执行操作
function doAction(list, index, action) {
const actions = { markAsDone, markAsUndone, updateTitle, remove }
actions[action] && actions[action](list, index)
}
function markAsDone(list, index) {
list[index].done = true
db.write(list)
}
function markAsUndone(list, index) {
list[index].done = false
db.write(list)
}
function updateTitle(list, index) {
inquirer.prompt({
type: 'input',
name: 'title',
message: '新的标题',
default: list[index].title
}).then(answer => {
list[index].title = answer.title
db.write(list)
})
}
function remove(list, index) {
list.splice(index, 1)
db.write(list)
}
// 创建任务
function createTask(list) {
inquirer.prompt({
type: 'input',
name: 'title',
message: '请输入任务标题'
}).then(answer => {
list.push({ title: answer.title, done: false })
db.write(list)
})
}
五、发布至 npm
配置 package.json:
{
"name": "node-todo-dong",
"bin": { // 指定命令行使用的命令
"t": "cli.js"
},
"files": ["*.js"], // 告诉 npm 哪些文件是有用的
"version": "0.0.1",
"description": "使用 Node.js fs 模块做一个命令行 todo 工具",
"main": "index.js",
"repository": "https://gitee.com/barrydong/node-todo.git",
"author": "BarryDong",
"license": "MIT",
"dependencies": {
"commander": "3.0.2",
"inquirer": "^8.2.4"
}
}
给 cli.js 头部加上 Node.js Shebang:
#!/usr/bin/env node
给 cli.js 添加可执行权限:
chmod +x cli.js
检查 npm 源是否是原始源,如果不是,需要切换到 npm 源:
nrm use npm
使用命令行登录 npm:
npm adduser
登录成功后发布:
npm publish