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: