preface

Learning objective: Implement a simple Node application: Todo List.

The main functions are:

  • Adding a New task
  • Clear your task list
  • Show all tasks
  • Operation tasks
    • Modifying the task title
    • Modifying task Status
    • Deleting a Single Task

One, environment installation

  1. node
  2. npm

2. Project initialization

  1. New Foldermkdir node-todo-1
  2. Go to the file directorycd node-todo-1
  3. Initialize the package filenpm init -y
  4. The new filetouch index.js

Third, commander. Js

Official website: github.com/tj/commande…

1. Install dependencies

yarn add commander
Copy the code

2. The options

// index.js
const { program } = require('commander');
program
  .option('-a --add'.'add an item')
  .option('-d, --delete'.'delete an item');

program.parse(process.argv);
Copy the code

.option(flags, desc) is used to define options

3. Look at the results

-h is short for –help and is used to display the help list by default.

node index.js -h
Copy the code

4. The command

Look at the document, copy and paste it, fumble around.

// index.js
const { program } = require('commander');
/ / options
program
  .option('-a --add'.'add an item')
  .option('-d, --delete'.'delete an item');
/ / command
program
  .command('add') // Enter node index add task1 task2 task3 in the terminal
  .argument('
      
        '
      .'taskNameList') // Multi-parameter processing
  .description('The cmd is used to add a task or more tasks.') // Command description
  .action((tasks) = > { 
    console.log(tasks); // tasks is arguments list
    // Merge the argument lists into strings
    const words = tasks.join(' ');
    console.log(words);
  })
program.parse(process.argv);
Copy the code

.command(nameAndArgs) is used to define commands. The command name and input parameters can be written together!

.argument(arg) is used to define arguments. Note that ‘<args… >’ form!

.description(desc) indicates the description of the command

.action(fn) is the callback executed after a command is entered

  • Node index add task1 task2 task3

4. Separation of callback

Rename index.js to cli.js, with the difference that the contents of.action() are separated into index.js for unified management.

Index.js hosts various apis. (Add new tasks, clear task list, show all tasks, etc.)

1. The Linux command

Cat ~/. Todo: View the contents of the. Todo file in the root directory

Rm ~/. Todo indicates that the. Todo files in the same directory are deleted

2. Add a new task (Function 1)

  • Import index.js and use the methods in it.
// cli.js
const { program } = require('commander');
const api = require('./index')

/ / options
program
  .option('-a --add'.'add an item')
  .option('-d, --delete'.'delete an item');
/ / command
program
  .command('add') // Enter node index add task1 task2 task3 in the terminal
  .argument('
      
        '
      .'taskNameList') // Multi-parameter processing
  .description('The cmd is used to add a task or more tasks.') // Command description
  .action((tasks) = > { 
    const words = tasks.join(' ');
    api.add(words); // Execute the add method to add a new task to the database!
  })
program
  .command('clear')
  .description('The cmd is used to clear all tasks.')
  .action((tasks) = > { 
    // Merge the argument lists into strings
    const words = tasks.join(' ');
    console.log(words);
  })
program.parse(process.argv);
Copy the code
  • The add() method is a method in index.js that defines the processing logic when the Add Commander command is triggered.
    • Read database file fs.readfile ()
    • Add a new task
    • Write the new task to fs.writefile ()
// index.js
const homedir = require('os').homedir(); // Get the home directory
const home = process.env.HOME || homedir; // Get it from the system variable
const path = require('path');
const dbPath = path.join(home, '.todo'); // Database path (concatenated)
const fs = require('fs');

module.exports.add = (taskContent) = > {
  // 1. Read files
  fs.readFile(dbPath, {flag: 'a+'}, (err, data) = > {
    if (err)  { 
      console.log(err); 
    } else {
      let list;
      try {
        // Data.toString () should be a JSON string that needs to be converted to a real array!
        list = JSON.parse(data.toString());
      } catch (error) {
        // If no such data exists, create a new array!
        list = [];
      }
      // add a task
      const task = {
        title: taskContent,
        completed: false
      }
      list.push(task); // Push the new task into the list
      // 3. Save the task to a file
      const string = JSON.stringify(list); // Convert the list to a JSON string
      // Write data to a file
      fs.writeFile(dbPath, string, (err) = > {
        if (err) {
          console.log(err);
          return; }})}})}Copy the code

Node.js has many built-in modules that can retrieve certain information from the host environment. Documentation: devdocs. IO /

  • OS moduleIs the operating system module, os.homedir() can get the system root directory path.

  • process.env.HOMEYou can get the path to the HOME variable set in the process.

  • The path modulePath.join (… Path can concatenate multiple paths.

  • Fs module is a file module,

    • Fs. readFile(filePath, options, (err, data) => {})Devdocs. IO/node ~ 14 _lts…

    • File write: fs.writefile (filePath, data, (err) => {})Devdocs. IO/node ~ 14 _lts…

Finally, try this:

3. Method encapsulation interface – oriented programming

Of the three steps in “Add task”, hopefully one is an execution statement instead of a bunch of redundant code sitting there!

That is to say, design the interface first, then encapsulate the code, later use a certain function, only need to call the corresponding interface.

  • Read/write operations in the database:
    • Note that fs.readfile () and fs.writefile () are asynchronous operations, so you cannot return the result directly.
    • Override asynchronous operations with Promise objects,
    • And return reject(err) when an error occurs. (Simply return the reason for the failure without executing the code below.)
// db.js
const homedir = require('os').homedir();
const home = process.env.HOME || homedir;
const path = require('path');
const dbPath = path.join(home, '.todo');
const fs = require('fs');

const db = {
  // 1. Read files
  read (path = dbPath) {
    return new Promise ((resolve, reject) = > {
      fs.readFile(path, {flag: 'a+'}, (err, data) = > {
        if (err) return reject(err);

        let list;
        try {
          list = JSON.parse(data.toString());
        } catch(error) { list = []; } resolve(list); })})},// 2. Write files
  write (list, path = dbPath) {
    return new Promise((resolve, reject) = > {
      const string = JSON.stringify(list);
      fs.writeFile(path, string, (err) = > {
        if (err) returnreject(err); resolve(); }}})})module.exports = db;
Copy the code
  • Interface call
// index.js
const db = require('./db');

module.exports.add = async (taskContent) => {
  // 1. Read files
  const list = await db.read();
  // add a task
  list.push({title: taskContent, completed: false});
  // 3. Write the task to a file
  await db.write(list);
}
Copy the code

Finally, try this:

4. Clear the task list (Function 2)

Write to an empty array:

// cli.js
const { program } = require('commander');
const api = require('./index')

/ / options
program
  .option('-a --add'.'add an item')
  .option('-d, --delete'.'delete an item');
/ / command
Command 1: add a new task
program
  .command('add') // Enter node index add task1 task2 task3 in the terminal
  .argument('
      
        '
      .'taskNameList') // Multi-parameter processing
  .description('The cmd is used to add a task or more tasks.') // Command description
  .action((tasks) = > { 
    const words = tasks.join(' ');
    api.add(words)
      .then(() = > {
          console.log('Added successfully! ');
        })
      .catch(err= > {
        console.log('Add failed! Error cause: ' + err);
      });
  })
// Command 2: Clear the task list
program
  .command('clear')
  .description('The cmd is used to clear all tasks.')
  .action(() = > { 
    api.clear()
      .then(() = > {
            console.log('Clear success! ');
          })
      .catch(err= > {
        console.log('Cleanup failed! Error cause: ' + err);
      });
  })
program.parse(process.argv);
Copy the code
// index.js
const db = require('./db');

// Add a new task
module.exports.add = async (taskContent) => {
  // 1. Read files
  const list = await db.read();
  // add a task
  list.push({title: taskContent, completed: false});
  // 3. Write the task to a file
  await db.write(list);
}

// Clear the task list
module.exports.clear = async (title) => {
  await db.write([]);
}
Copy the code

Finally, try this:

5. Show all tasks (Function 3)

Process. argv indicates the number of parameters entered by the user in the terminal.

When only two node cli.js parameters are entered, all tasks are displayed:

// cli.js
// The user invokes Node cli.js directly
if (process.argv.length === 2) {
  void api.showAll();
} else {
  program.parse(process.argv);
}
Copy the code
// inidex.js
// ...
// Display all tasks
module.exports.showAll = async() = > {// 1. Read the previous task
  const list = await db.read();
  // 2. Print the direct task
  list.forEach((task, index) = > {
    console.log(`${task.completed ? '[x]' : '[_]'} ${index + 1} -> ${task.title}`);
  });
}
Copy the code

Five, the inquirer

Inquirer is a user – command line interaction tool. Github.com/SBoudrias/I…

1. Install dependencies

yarn add inquirer
Copy the code

When all the tasks are displayed, you actually need to move the cursor up and down to perform subsequent actions, so use the Inquirer library to do this!

2. Operation Tasks (Function 4)

// index.js
const db = require('./db');
const inquirer = require('inquirer');



// Add a new task
module.exports.add = async (taskContent) => {
  // 1. Read files
  const list = await db.read();
  // add a task
  list.push({title: taskContent, completed: false});
  // 3. Write the task to a file
  await db.write(list);
}

// Clear the task list
module.exports.clear = async() = > {await db.write([]);
}

// Display all items
module.exports.showAll = async() = > {// 1. Read the previous task
  const list = await db.read();
  // 2. Print the previous task
  // list.forEach((task, index) => {
  // console.log(`${task.completed ? '[x]' : '[_]'} ${index + 1} -> ${task.title}`);
  // });
	
  // Initiate an inquiry
  inquirer
    .prompt({
        type: 'list'.name: 'index'.message: 'Which task do you want to perform? '.choices: [{name: '+ Add task '.value: '2'},
          { name: '- out'.value: '1'},
          ...list.map((task, index) = > {
            return { name: `${task.completed ? '[x]' : '[_]'} ${index + 1} -> ${task.title}`.value: index }
          })
        ],
      })
    .then((answer) = > {
      const index = parseInt(answer.index);
      if (index >= 0) {
        // A task is selected
        inquirer.prompt({
          type: 'list'.name: 'action'.message: 'Please select operation'.choices: [{name: 'exit'.value: 'quit'},
            {name: 'Done'.value: 'completed'},
            {name: 'not done'.value: 'incomplete'},
            {name: 'Change the title'.value: 'updateTitle'},
            {name: 'delete'.value: 'remove'},
          ]
        }).then(answer= > {
          console.log(answer.action);
          switch (answer.action) {
            case 'completed':
              list[index].completed = true;
              db.write(list)
              break;
            case 'incomplete':
              list[index].completed = false;
              db.write(list)
              break;
            case 'updateTitle':
              inquirer.prompt({
                type: 'input'.name: 'title'.message: 'Please enter a new title'.default: list[index].title / / the original 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) {
        // Add a task
        inquirer.prompt({
          type: 'input'.name: 'title'.message: 'Please add a new task title',
        }).then(answer= > {
          list.push({
            title: answer.title,
            completed: false}) db.write(list); }}})); }Copy the code

3. Code optimization

// index.js
const db = require('./db');
const inquirer = require('inquirer');

// 1. Add a task
module.exports.add = async (taskContent) => {
  // 1. Read files
  const list = await db.read();
  // add a task
  list.push({title: taskContent, completed: false});
  // 3. Write the task to a file
  await db.write(list);
}

// 2. Clear the task list
module.exports.clear = async() = > {await db.write([]);
}

3.2.2 Adding a New Task
function askForAddNewTask (list) {
  inquirer.prompt({
    type: 'input'.name: 'title'.message: 'Please add a new task title',
  }).then(answer= > {
    list.push({
      title: answer.title,
      completed: false
    })
    db.write(list);
    console.log('Added successfully! '); })}// 3.2.1.1 Setting completed Status
async function setCompletedState (list, index) {
  list[index].completed = true;
  await db.write(list);
  console.log('Current task completed! ');
}
// 3.2.1.2 Setting incomplete Status
async function setIncompleteState (list, index) {
  list[index].completed = false;
  await db.write(list);
  console.log('Current task to complete... ');
}
// 3.2.1.3 Modify the title
function updateTitle (list, index) {
  inquirer.prompt({
    type: 'input'.name: 'title'.message: 'Please enter a new title'.default: list[index].title / / the original title
  }).then(answer= > {
    list[index].title = answer.title;
    db.write(list);
    console.log('Title updated successfully! ');
  });
}
3.2.1.4 Removing a Task
async function removeTask (list, index) {
  list.splice(index, 1);
  await db.write(list);
  console.log('Deleted successfully! ');
}

// 3.2.1 Subsequent Operations
function askForNextAction (list, index) {
  const actions = {
    setCompletedState,
    setIncompleteState,
    updateTitle,
    removeTask
  }
  inquirer.prompt({
    type: 'list'.name: 'action'.message: 'Please select operation'.choices: [{name: 'exit'.value: 'quit'},
      {name: 'Done'.value: 'setCompletedState'},
      {name: 'not done'.value: 'setIncompleteState'},
      {name: 'Change the title'.value: 'updateTitle'},
      {name: 'delete'.value: 'removeTask'},
    ]
  }).then(answer= > {
    const currentAction = actions[answer.action];
    currentAction && currentAction(list, index);

    // switch (answer.action) {
    // case 'setCompletedState':
    // setCompletedState(list, index);
    // break;
    // case 'setIncompleteState':
    // setIncompleteState(list, index);
    // break;
    // case 'updateTitle':
    // updateTitle(list, index);
    // break;
    // case 'removeTask':
    // removeTask(list, index);
    // break;
    // }})}// 3.2 Printing previous tasks + Follow-up Operations
function displayTasks (list) {
  inquirer
  .prompt({
      type: 'list'.name: 'index'.message: 'Which task do you want to perform? '.choices: [{name: '+ Add task '.value: '2'},
        { name: '- out'.value: '1'},
        ...list.map((task, index) = > {
          return { name: `${task.completed ? '[x]' : '[_]'} ${index + 1} -> ${task.title}`.value: index }
        })
      ],
    })
  .then((answer) = > {
    const index = parseInt(answer.index);
    if (index >= 0) {
      // 3.2.1 A task is selected and perform subsequent operations
      askForNextAction(list, index);
    } else if (index === -2) {
      3.2.2 Adding a New TaskaskForAddNewTask(list); }}); }// 3. Display all items
module.exports.showAll = async() = > {// 3.1 Read out the previous task
  const list = await db.read();
  // 3.2 Print the previous task
  displayTasks(list);
}
Copy the code

Vi. Code release

1. Set the shebang

Allow the user to automatically perform the node, reference: zhuanlan.zhihu.com/p/262456371

Add a shebang code to the first line of cli.js:

// cli.js
#!/usr/bin/env node
Copy the code

2. The configuration package. Json

{
  "name": "cpc-node-todo-1"."bin": {
    "cpc-todo": "./cli.js"
  },
  "files": [
    "cli.js"."db.js"."index.js"]."version": "0.0.3"."main": "index.js"."license": "MIT"."dependencies": {
    "commander": "^ 8.0.0." "."inquirer": "^ 8.1.2"}}Copy the code
  • The name field represents the name of the package to be downloaded and used later.

  • The bin field indicates the command that can be run directly on the terminal.

  • The files field indicates the files to be uploaded. “Files “: [“*.js”] “files”: [“*.js”] “files”: [“*.js”]

3. Publish to NPM

  1. NPM login (This step requires a user name and password, and an email address.)

  2. npm publish

  3. npm logout

4. Download and use

  1. Open terminal, global installation: NPM i-g CPC-node-todo-1

  2. Once installed, call the Node application through the cpC-todo. Note that the cpC-todo is the bin field set in package.json before packaging.

  3. NPM un-g CPC -todo-1

5. Package updates

A little.

Unit testing

1. jest

www.jestjs.cn/docs/gettin…

npm install --save-dev jest
Copy the code

To be updated…