前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【自学总结】基于 node 写一个 todo 命令行工具

【自学总结】基于 node 写一个 todo 命令行工具

作者头像
前端小智@大迁世界
发布2021-01-12 10:19:51
6190
发布2021-01-12 10:19:51
举报
文章被收录于专栏:终身学习者终身学习者

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

前言

最后公司官网要用 node 重构,所以这段时间开始在学习 node 系列知识,一般对于新东西的上手,就是做一个 todo 的功能,所以这里也是基于 node 写一个 todo 命令行工具。

最终成品:https://github.com/qq44924588...

命令行相关的库

commander.js

node.js 命令行界面的完整解决方案是Ruby中Commandernode.js中的实现。 为commandline程序提供强大的参数解析能力.是TJ所写的一个工具包,其作用是让node命令行程序的制作更加简单。

具体用法请参考 github: https://github.com/tj/command...

inquirer.js

inquirer.js 一个用户与命令行交互的工具

开始通过npm init 创建package.json的时候就有大量与用户的交互(当然也可以通过参数来忽略输入);而现在大多数工程都是通过脚手架来创建的,使用脚手架的时候最明显的就是与命令行的交互,如果想自己做一个脚手架或者在某些时候要与用户进行交互,这个时候就不得不提到inquirer.js了。

具体用法请参考 github: https://github.com/SBoudrias/...

初始化

1.运行yarn init -y,添加package.json文件

2.运行yarn add commander,引入commander

3.创建cli.js

使用 commander 创建子命令,这里指写一个子命令的使用方式,具体可以参考 github 上的项目

代码语言:javascript
复制
const program = require('commander');
program
 .command('add')
 .description('add a task')
 .action(() => {
   console.log('添加任务');
 });

4.运行node cli add,node + 文件名 + 子命令,输出添加任务

5.对于数据的保存这里没有使用数据库存,简单的使用了文件,为此,我们需要封装文件相关的读和写操作,创建 db.js,内容如下

代码语言:javascript
复制
const homedir = require('os').homedir()
// 优先获取用户设置的 home
const home = process.env.HOME || homedir
const p = require('path')
const fs = require('fs')
const dbPath = p.join(home, '.todo')

const db = {
  read (path = dbPath) {
    // 读取之前的任务
    return new Promise((resolve, reject) => {
      fs.readFile(path, {
        flag: 'a+'
      }, (error, data) => {
        if (error) return reject(error)
        let list
        try{
          list = JSON.parse(data.toString())
        } catch(error2) {
          list = []
        }
        resolve(list)
      })
    })
  },
  write (list, path = dbPath) {
    return new Promise((resolve, reject) => {
      const string = JSON.stringify(list)
      fs.writeFile(path, string, (error) => {
        if (error) return reject(error)
        resolve()
      })
    })
  }
}

module.exports = db

这里我们在 home环境变量路径创建了一个 .todo 的文件,用来保存我们输入的数据。

对于 node Api 的查询,这里推荐一个网站 https://devdocs.io/,可以很方便查到对应方法使用方式,如下图所示:

6.任务的增删改都可以看作是一个接口,为此创建 index.js 用来封闭这些操作,这也叫作 面向接口编程,这里我们举例 添加方法的实现方式:

代码语言:javascript
复制
const db = require('./db.js')

module.exports.add = async (title) => {
  // 读取之前的任务
  const list = await db.read()
  // 往里面添加一个 title 任务
  list.push({title, done: false})
  // 存储任务到文件
  await db.write(list)
}

测试

输入node cli.js add 买水

查看:cat ~/.todo

输出[{"title":"买水","done":false}]

7.显示所有任务

在 cli.js 添加一条子命令 list

代码语言:javascript
复制
program
.command('list')
.description('show all todo list')
.action((...args) => {
  api.showAll().then(res => {
    // console.log('显示任务完毕!')
  }, () => {
    console.log('显示任务失败!')
  })
});

在 index.js 中实现 showAll() 方法

代码语言:javascript
复制
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 ? '[x]' : '[_]'} ${index + 1} - ${task.title}`, value: index.toString()}
      }), {name: '+ 创建任务', value: '-2'}]
    })
    .then(answer => {
      const index = parseInt(answer.index)
      if(index >= 0) {
        //选中了一个任务
        inquirer.prompt({
          type: 'list',
          name: 'action',
          choices: [
            {name: '退出', value: 'quit'},
            {name: '已完成', value: 'markAsDone'},
            {name: '未完成', value: 'markAsUndone'},
            {name: '改标题', vaule: 'updateTitel'},
            {name: '删除', value: 'remove'}
          ]
        }).then(answer2 => {
          switch (answer2.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
          }
        })
      }else if(index === -2) {
        //创建任务
        inquirer.prompt({
          type: 'input',
          name: 'title',
          message: '输入任务标题',
        }).then(answer => {
          list.push({
            title: answer.title,
            done: false
          })
          db.write(list)
        })
      }
    });
}

然后运行 node cli.js list 就可以显示所有任务了,这里列出这个方法主要是分享一下如何来优化这段冗长的代码。

优化代码

封装函数,重命名,提高代码可读性

我们先把选中一个任务也就是 index >=0 的逻辑先全部提取出来,放到函数 askForAction,然后把每个 switch case 的逻辑也都封装成一个函数

代码语言:javascript
复制
function askForAction (list, index) {
  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 => {
    switch (answer.action) {
      case 'markAsDone':
        markAsDone(list, index)
        break;
      case 'markAsUnDone':
        markAsUnDone()
        break;
      case 'updateTitle':
        updateTitle()
        break;
      case 'remove':
        remove()
        break;
    }
  })
}

上面的 case 条件与我们函数又一致,所以可以在进一步优化,如下所示:

代码语言:javascript
复制
function askForAction (list, index) {
  // 选中一个任务
  const actions = {
    markAsDone,
    markAsUnDone,
    updateTitle,
    updateTitle,
    remove
  }
  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 => {
    const action = actions[actions.action]
    action && action(list, index)
  })
}

在重构中,大部分的 switch 是可以被优化成更多简单的模式。

发布到 npm

配置 package.json

代码语言:javascript
复制
{
  "name": "hi-node-todo",
  "bin": {
    "t": "cli.js"
  },
  "files": [
    "*.js"
  ],
  "version": "0.0.1",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "commander": "^6.2.1",
    "inquirer": "^7.3.3"
  }
}

bin 的作用

代码语言:javascript
复制
很多包都有一个或多个可执行的文件希望被放到PATH中。(实际上,就是这个功能让npm可执行的)。要用这个功能,给`package.json`中的bin字段一个命令名到文件位置的`map`。初始化的时候npm会将他链接到`prefix/bin`(全局初始化)或者`./node_modules/.bin/`(本地初始化)。
代码语言:javascript
复制
{ "bin" : { "npm" : "./cli.js" } }

当你初始化npm,它会创建一个符号链接到cli.js脚本到/usr/local/bin/npm。如果你只有一个可执行文件,并且名字和包名一样。那么你可以只用一个字符串,比如

代码语言:javascript
复制
{ "name": "my-program" , "version": "1.2.5" , "bin": "./path/to/program" }

// 等价于

代码语言:javascript
复制
{ "name": "my-program" , "version": "1.2.5" , "bin" : { "my-program" : "./path/to/program" } }

files 的作用

files是一个包含项目中的文件的数组。如果命名了一个文件夹,那也会包含文件夹中的文件。(除非被其他条件忽略了)你也可以提供一个.npmignore文件,让即使被包含在files字段中得文件被留下。其实就像.gitignore一样。

代码语言:javascript
复制
{ "files": [ "bin/", "templates/", "test/" ]}

main 的作用

main字段是一个模块ID,它是一个指向你程序的主要项目。就是说,如果你包的名字叫foo,然后用户安装它,然后require("foo"),然后你的main模块的exports对象会被返回。这应该是一个相对于根目录的模块ID。对于大多数模块,它是非常有意义的,其他的都没啥。

代码语言:javascript
复制
{ "main": "bin/index.js"}

发布:

  1. npm adduser
  2. npm publish

用户使用:

  1. yarn global add node-todos
  2. t list

版本升级: 增加查看版本的功能

cli.js

代码语言:javascript
复制
const pkg = require('./package.json')
program
  .version(pkg.version)

测试,输入node cli.js --version,输出0.0.1

重新发布,直接npm publish

用户使用时,更新包yarn global add node-todos@0.0.2,运行t --version,输出0.0.2

完~,我是小智,我们下期见!

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 命令行相关的库
    • commander.js
      • inquirer.js
      • 初始化
      • 优化代码
      • 发布到 npm
      相关产品与服务
      命令行工具
      腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档