Aplicación CLI Todo List usando Node.js

CLI es una herramienta muy poderosa para los desarrolladores. Aprenderemos cómo crear una aplicación simple de lista de tareas para la línea de comandos. Hemos visto TodoList como un proyecto para principiantes en desarrollo web y desarrollo de Android, pero una aplicación CLI es algo de lo que no escuchamos con frecuencia.

Requisitos previos:

  • Una versión reciente de Node.js descargada e instalada.
  • Un editor de texto, por ejemplo, VSCode, atom, etc.
  • Se debe instalar NPM.
  • Comprensión básica de JavaScript, características de es6, etc.

Cree un nuevo directorio con mkdir <dir-name> e inicie un proyecto de Node vacío con cd en el directorio y escriba el comando npm init.

Nota: No utilizaremos ningún módulo de terceros en este proyecto.

Directorio del proyecto: ahora el directorio del proyecto debe constar de dos archivos, package.json e index.js, como se muestra a continuación:

Algunas funcionalidades básicas en nuestra aplicación Todo List serán las siguientes:

Implementación: 

Cree un archivo index.js y escriba el siguiente código en él

Javascript

// Requiring module
const fs = require('fs');
  
// Accessing arguments
const args = process.argv;
  
// The "index.js" is 8 characters long 
// so -8 removes last 8 characters
const currentWorkingDirectory = args[1].slice(0, -8);

Explicación: Escribiremos tareas pendientes en un archivo llamado todo.txt y cuando se completen, se borrarán de todo.txt y se escribirán en done.txt . El informe mostrará cuántas tareas se han completado y cuántas quedan. Para empezar, importaremos el módulo fs que nos dará acceso al sistema de archivos.

Luego, pasaremos los argumentos usando process.argv y los almacenaremos en una variable llamada args . El proceso.argv devuelve una array en la que se encuentra la ubicación de los módulos de Node, el directorio de trabajo actual y otros argumentos pasados. Recuperaremos el cwd eliminando el nombre del archivo.

A continuación, comprobaremos si todo.txt y done.txt ya existen en cwd, si no, los crearemos como se muestra a continuación:

Javascript

if (fs.existsSync(currentWorkingDirectory + 'todo.txt') === false) {
  let createStream = fs.createWriteStream('todo.txt');
  createStream.end();
}
if (fs.existsSync(currentWorkingDirectory + 'done.txt') === false) {
  let createStream = fs.createWriteStream('done.txt');
  createStream.end();
}

Ahora crearemos diferentes funciones para mostrar el uso, agregar tareas pendientes, eliminar tareas pendientes, etc. Se llamarán en función de los argumentos pasados.

Función de información: mostrará el formato de uso. Se llamará cuando se pase ayuda como argumento o cuando no se pase ningún argumento.

Javascript

const InfoFunction = () => {
  const UsageText = `
Usage :-
$ node index.js add "todo item"  # Add a new todo
$ node index.js ls               # Show remaining todos
$ node index.js del NUMBER       # Delete a todo
$ node index.js done NUMBER      # Complete a todo
$ node index.js help             # Show usage
$ node index.js report           # Statistics`;
  
  console.log(UsageText);
};

Función de lista: leerá los datos de todo.txt y los mostrará con un número correspondiente. Más reciente se muestra en la parte superior con el número más grande.

Javascript

const listFunction = () => {
  
  // Create a empty array
  let data = []; 
    
  // Read from todo.txt and convert it
  // into a string
  const fileData = fs.readFileSync(
    currentWorkingDirectory + 'todo.txt')
  .toString(); 
    
  // Split the string and store into array
  data = fileData.split('\n'); 
    
  // Filter the string for any empty lines in the file
  let filterData = data.filter(function (value) {
    return value !== '';
  }); 
    
  if (filterData.length === 0) {
    console.log('There are no pending todos!');
  }
    
  for (let i = 0; i < filterData.length; i++) {
    console.log((filterData.length - i) + '. ' 
    + filterData[i]);
  }
};

Agregar función: leerá el contenido de todo.txt, agregará el nuevo todo y luego lo reescribirá en todo.txt.

Nota: Dado que no tenemos la funcionalidad para establecer la posición del puntero en el archivo mientras escribimos y leemos archivos en JavaScript, cada vez que agreguemos nuevos datos tendremos que leer los datos, hacer las modificaciones y luego volver a escribir de vuelta

Javascript

const addFunction = () => {
  
  // New todo string argument is stored
  const newTask = args[3]; 
    
  // If argument is passed
  if (newTask) { 
    
    // Create a empty array
    let data = []; 
  
    // Read the data from file todo.txt and 
    // convert it in string
    const fileData = fs
      .readFileSync(currentWorkingDirectory + 'todo.txt')
      .toString(); 
        
    // New task is added to previous data  
    fs.writeFile(
      currentWorkingDirectory + 'todo.txt',
      newTask + '\n' + fileData, 
        
      function (err) {
  
        // Handle if there is any error
        if (err) throw err; 
          
        // Logs the new task added
        console.log('Added todo: "' + newTask + '"'); 
      },
    );
  } else { 
  
    // If argument was no passed
    console.log('Error: Missing todo string. Nothing added!');
  }
};

Función de eliminación: leerá los datos de todo.txt , eliminará la tarea correspondiente y volverá a escribir los datos en un archivo.

Javascript

const deleteFunction = () => {
  
  // Store which index is passed
  const deleteIndex = args[3]; 
    
  // If index is passed
  if (deleteIndex) { 
    
    // Create a empty array
    let data = []; 
      
    // Read the data from file and convert
    // it into string
    const fileData = fs
      .readFileSync(currentWorkingDirectory + 'todo.txt')
      .toString(); 
        
    data = fileData.split('\n');
      
    // Filter the data for any empty lines
    let filterData = data.filter(function (value) {
      return value !== ''; 
    });
      
    // If delete index is greater than no. of task 
    // or less than zero
    if (deleteIndex > filterData.length || deleteIndex <= 0) {
      console.log(
        'Error: todo #' + deleteIndex 
            + ' does not exist. Nothing deleted.',
      );   
    } else {
  
      // Remove the task
      filterData.splice(filterData.length - deleteIndex, 1); 
        
      // Join the array to form a string
      const newData = filterData.join('\n'); 
        
      // Write the new data back in file
      fs.writeFile(
        currentWorkingDirectory + 'todo.txt',
        newData, 
        function (err) {
          if (err) throw err;
  
          // Logs the deleted index
          console.log('Deleted todo #' + deleteIndex); 
        },
      );
    }
  } else { 
  
    // Index argument was no passed
    console.log(
'Error: Missing NUMBER for deleting todo.');
  }
};

Función Done: esta función leerá los datos de todo.txt , los dividirá en una array, almacenará la tarea para marcarla como completada, la eliminará de todo.txt y volverá a escribir los datos en todo.txt . Ahora escribiremos la tarea eliminada que almacenamos junto con la fecha actual en done.txt.

Javascript

const doneFunction = () => {
  
  // Store the index passed as argument
  const doneIndex = args[3]; 
    
  // If argument is passed
  if (doneIndex) { 
    
    // Empty array
    let data = []; 
      
    // Create a new date object
    let dateobj = new Date(); 
      
    // Convert it to string and slice only the
    // date part, removing the time part
    let dateString = dateobj.toISOString().substring(0, 10); 
      
    // Read the data from todo.txt
    const fileData = fs
      .readFileSync(currentWorkingDirectory + 'todo.txt')
      .toString(); 
      
    // Read the data from done.txt
    const doneData = fs
      .readFileSync(currentWorkingDirectory + 'done.txt')
      .toString(); 
        
    // Split the todo.txt data
    data = fileData.split('\n'); 
      
    // Filter for any empty lines
    let filterData = data.filter(function (value) {
      return value !== '';
    }); 
      
    // If done index is greater than no. of task or <=0
    if (doneIndex > filterData.length || doneIndex <= 0) {
      console.log('Error: todo #' 
          + doneIndex + ' does not exist.');
    } else {
  
      // Delete the task from todo.txt
      // data and store it
      const deleted = filterData.splice(
          filterData.length - doneIndex, 1);
        
      // Join the array to create a string
      const newData = filterData.join('\n'); 
        
      // Write back the data in todo.txt
      fs.writeFile(
        currentWorkingDirectory + 'todo.txt',
        newData, 
        function (err) {
          if (err) throw err;
        },
      );
        
      // Write the stored task in done.txt
      // along with date string
      fs.writeFile( 
        currentWorkingDirectory + 'done.txt',
        'x ' + dateString + ' ' + deleted 
         + '\n' + doneData,
        function (err) {
          if (err) throw err;
          console.log('Marked todo #' 
            + doneIndex + ' as done.');
        },
      );
    }
  } else { 
  
    // If argument was not passed
    console.log('Error: Missing NUMBER for'
        + ' marking todo as done.');
  }
};

Función de informe: leerá datos de todo.txt y done.txt , calculará la cantidad de tareas en cada uno y mostrará cuántas tareas se completaron y cuántas están pendientes.

Javascript

const reportFunction = () => {
  
    // Create empty array for data of todo.txt
    let todoData = [];
  
    // Create empty array for data of done.txt
    let doneData = [];
  
    // Create a new date object
    let dateobj = new Date();
  
    // Slice the date part
    let dateString = dateobj.toISOString().substring(0, 10);
  
    // Read data from both the files
    const todo = fs.readFileSync(currentWorkingDirectory
                + 'todo.txt').toString();
    const done = fs.readFileSync(currentWorkingDirectory
                + 'done.txt').toString();
  
    // Split the data from both files
    todoData = todo.split('\n');
  
    doneData = done.split('\n');
    let filterTodoData = todoData.filter(function(value) {
        return value !== '';
    });
  
    let filterDoneData = doneData.filter(function(value) {
  
        // Filter both the data for empty lines
        return value !== '';
    });
  
    console.log(
        dateString +
        ' ' +
        'Pending : ' +
        filterTodoData.length +
        ' Completed : ' +
        filterDoneData.length,
        // Log the stats calculated
    );
};

Ahora que hemos creado todas las funciones, ahora solo colocaremos una declaración de cambio y llamaremos a las funciones en función de los argumentos pasados.

Javascript

switch (args[2]) {
  case 'add': {
    addFunction();
    break;
  }
  
  case 'ls': {
    listFunction();
    break;
  }
  
  case 'del': {
    deleteFunction();
    break;
  }
    
  case 'done': {
    doneFunction();
    break;
  }
    
  case 'help': {
    InfoFunction();
    break;
  }
    
  case 'report': {
    reportFunction();
    break;
  }
    
  default: {
    InfoFunction();
    // We will display help when no 
    // argument is passed or invalid
    // argument  is passed
  }
}

Nombre de archivo: index.js Nuestro archivo index.js final tendrá el siguiente aspecto:

Javascript

const fs = require('fs');
  
const args = process.argv;
  
// The "index.js" is 8 characters long so -8
// removes last 8 characters
const currentWorkingDirectory = args[1].slice(0, -8);
  
  
if (fs.existsSync(currentWorkingDirectory +
        'todo.txt') === false) {
    let createStream = fs.createWriteStream('todo.txt');
    createStream.end();
}
if (fs.existsSync(currentWorkingDirectory +
        'done.txt') === false) {
    let createStream = fs.createWriteStream('done.txt');
    createStream.end();
}
  
const InfoFunction = () => {
    const UsageText = `
Usage :-
$ node index.js add "todo item"  # Add a new todo
$ node index.js ls               # Show remaining todos
$ node index.js del NUMBER       # Delete a todo
$ node index.js done NUMBER      # Complete a todo
$ node index.js help             # Show usage
$ node index.js report           # Statistics`;
  
    console.log(UsageText);
};
  
const listFunction = () => {
  
    // Create a empty array
    let data = [];
  
    // Read from todo.txt and convert it into a string
    const fileData = fs
        .readFileSync(currentWorkingDirectory +
            'todo.txt').toString();
  
    // Split the string and store into array
    data = fileData.split('\n');
  
    // Filter the string for any empty lines in the file
    let filterData = data.filter(function(value) {
        return value !== '';
    });
  
    if (filterData.length === 0) {
        console.log('There are no pending todos!');
    }
    for (let i = 0; i < filterData.length; i++) {
        console.log((filterData.length - i) + '. ' +
            filterData[i]);
    }
};
  
const addFunction = () => {
  
    // New todo string argument is stored
    const newTask = args[3];
  
    // If argument is passed
    if (newTask) {
  
        // create a empty array
        let data = [];
  
        // Read the data from file todo.txt and
        // convert it in string
        const fileData = fs
            .readFileSync(currentWorkingDirectory +
                'todo.txt').toString();
  
        // New task is added to previous data
        fs.writeFile(
            currentWorkingDirectory + 'todo.txt',
            newTask + '\n' + fileData,
  
            function(err) {
  
                // Handle if there is any error
                if (err) throw err;
  
                // Logs the new task added
                console.log('Added todo: "' + newTask + '"');
            },
        );
    } else {
  
        // If argument was no passed
        console.log('Error: Missing todo string.' +
            ' Nothing added!');
    }
};
  
const deleteFunction = () => {
  
    // Store which index is passed
    const deleteIndex = args[3];
  
    // If index is passed
    if (deleteIndex) {
  
        // Create a empty array
        let data = [];
  
        // Read the data from file and convert
        // it into string
        const fileData = fs
            .readFileSync(currentWorkingDirectory +
                'todo.txt').toString();
  
        data = fileData.split('\n');
        let filterData = data.filter(function(value) {
  
            // Filter the data for any empty lines
            return value !== '';
        });
  
        // If delete index is greater than no. of task
        // or less than zero
        if (deleteIndex > filterData.length || deleteIndex <= 0) {
            console.log(
                'Error: todo #' + deleteIndex +
                ' does not exist. Nothing deleted.',
            );
  
        } else {
              
            // Remove the task
            filterData.splice(filterData.length - deleteIndex, 1);
              
            // Join the array to form a string
            const newData = filterData.join('\n');
              
            // Write the new data back in file
            fs.writeFile(
                currentWorkingDirectory + 'todo.txt',
                newData, 
                function(err) {
                    if (err) throw err;
  
                    // Logs the deleted index
                    console.log('Deleted todo #' + deleteIndex);
                },
            );
        }
    } else {
  
        // Index argument was no passed
        console.log('Error: Missing NUMBER for deleting todo.');
    }
};
  
const doneFunction = () => {
      
    // Store the index passed as argument
    const doneIndex = args[3];
      
    // If argument is passed
    if (doneIndex) {
          
        // Empty array
        let data = [];
          
        // Create a new date object
        let dateobj = new Date();
          
        // Convert it to string and slice only the
        //  date part, removing the time part
        let dateString = dateobj.toISOString()
                    .substring(0, 10);
          
        // Read the data from todo.txt
        const fileData = fs
            .readFileSync(currentWorkingDirectory
                + 'todo.txt').toString();
          
        // Read the data from done.txt
        const doneData = fs
            .readFileSync(currentWorkingDirectory
                + 'done.txt').toString();
          
        // Split the todo.txt data
        data = fileData.split('\n');
          
        // Filter for any empty lines
        let filterData = data.filter(function(value) {
            return value !== '';
        });
          
        // If done index is greater than
        // no. of task or <=0
        if (doneIndex > filterData.length || doneIndex <= 0) {
            console.log('Error: todo #' + doneIndex 
                    + ' does not exist.');
              
        } else {
              
            // Delete the task from todo.txt data
            // and store it
            const deleted = filterData.splice(
                filterData.length - doneIndex, 1);
              
            // Join the array to create a string
            const newData = filterData.join('\n');
              
            // Write back the data in todo.txt
            fs.writeFile(
                currentWorkingDirectory + 'todo.txt',
                newData,
                  
                function(err) {
                    if (err) throw err;
                },
            );
            fs.writeFile(
  
                // Write the stored task in done.txt
                // along with date string
                currentWorkingDirectory + 'done.txt',
                'x ' + dateString + ' ' + deleted 
                                + '\n' + doneData,
                function(err) {
                    if (err) throw err;
                    console.log('Marked todo #' 
                        + doneIndex + ' as done.');
                },
            );
        }
    } else {
        // If argument was not passed
        console.log('Error: Missing NUMBER for '
                +  'marking todo as done.');
    }
};
  
const reportFunction = () => {
      
    // Create empty array for data of todo.txt
    let todoData = [];
      
    // Create empty array for data of done.txt
    let doneData = [];
      
    // Create a new date object
    let dateobj = new Date();
      
    // Slice the date part
    let dateString = dateobj.toISOString()
                    .substring(0, 10);
      
    // Read data from both the files
    const todo = fs.readFileSync(
            currentWorkingDirectory 
            + 'todo.txt').toString();
  
    const done = fs.readFileSync(
        currentWorkingDirectory 
        + 'done.txt').toString();
  
    // Split the data from both files
    todoData = todo.split('\n');
      
    doneData = done.split('\n');
    let filterTodoData = todoData.filter(function(value) {
        return value !== '';
    });
    let filterDoneData = doneData.filter(function(value) {
        return value !== '';
        // Filter both the data for empty lines
    });
    console.log(
        dateString +
        ' ' +
        'Pending : ' +
        filterTodoData.length +
        ' Completed : ' +
        filterDoneData.length,
        // Log the stats calculated
    );
};
  
switch (args[2]) {
    case 'add':
        {
            addFunction();
            break;
        }
  
    case 'ls':
        {
            listFunction();
            break;
        }
  
    case 'del':
        {
            deleteFunction();
            break;
        }
  
    case 'done':
        {
            doneFunction();
            break;
        }
  
    case 'help':
        {
            InfoFunction();
            break;
        }
  
    case 'report':
        {
            reportFunction();
            break;
        }
  
    default:
        {
            InfoFunction();
            // We will display help when no 
            // argument is passed or invalid
            // argument  is passed
        }
}

Paso para ejecutar la aplicación:

Ejecute el archivo index.js con el siguiente comando:

node index.js

Producción:

Publicación traducida automáticamente

Artículo escrito por apoorvd14 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *