Una de mis cosas favoritas de Node es lo fácil que es crear herramientas simples de interfaz de línea de comandos (CLI). Entre el análisis de argumentos con yargs para administrar herramientas con npm, Node simplemente lo hace fácil.
Algunos ejemplos de los tipos de herramientas a los que me refiero son:
Cuando se instala (con el -g
opción), estos paquetes se pueden ejecutar desde cualquier lugar en la línea de comandos y funcionan de manera muy similar a las herramientas integradas de Unix.
He estado creando algunas aplicaciones Node.js para la línea de comandos últimamente y pensé que podría ser útil escribir una publicación sobre ellas para ayudarlo a comenzar. Entonces, a lo largo de este artículo, le mostraré cómo crear una herramienta de línea de comandos para obtener datos de ubicación para direcciones IP y URL.
Si ha visto el artículo de Abuso de pila sobre el aprendizaje de Node.js, puede recordar que creamos un paquete llamado twenty
que tenía una funcionalidad similar. Partiremos de ese proyecto y lo convertiremos en una herramienta CLI adecuada con más funcionalidad.
Configurar el proyecto
Contenido
Comencemos creando un nuevo directorio y configurando el proyecto usando npm:
Te puede interesar:Cómo usar PGP en Camel Routes$ mkdir twenty
$ npm init
Presione enter para todas las indicaciones en el último comando, y debería tener su package.json
archivo.
Tenga en cuenta que dado que ya he tomado el nombre del paquete twenty
en npm, tendrá que cambiarle el nombre por otro si realmente desea publicar. O también podrías alcance tu proyecto.
Luego, crea el index.js
archivo:
$ touch index.js
Esto es todo lo que realmente necesitamos para comenzar por ahora, y agregaremos al proyecto a medida que avancemos.
Analizar argumentos
La mayoría de las aplicaciones CLI aceptan argumentos del usuario, que es la forma más común de obtener información. En la mayoría de los casos, analizar los argumentos no es demasiado difícil, ya que generalmente solo hay un puñado de comandos y banderas. Pero a medida que la herramienta se vuelve más compleja, se agregarán más indicadores y comandos, y el análisis de argumentos puede volverse sorprendentemente difícil.
Para ayudarnos con esto, usaremos un paquete llamado yargs
, que es el sucesor del popular optimista paquete.
yargs
fue creado para ayudarlo a analizar los comandos del usuario, como este:
var argv = require('yargs').argv;
Ahora cadenas de opciones complejas como node index.js install -v --a=22 -cde -x derp
se puede acceder fácilmente:
var argv = require('yargs').argv;
argv._[0] // 'install'
argv.v // true
argv.a // 22
argv.c // true
argv.d // true
argv.e // true
argv.x // 'derp'
yargs
incluso lo ayudará a especificar la interfaz de comando, por lo que si la entrada del usuario no cumple con ciertos requisitos, le mostrará un mensaje de error. Entonces, por ejemplo, podemos decir yargs
queremos al menos 2 argumentos:
var argv = require('yargs')
.demand(2)
.argv
Y si el usuario no proporciona al menos dos, verá este mensaje de error predeterminado:
$ node index.js foo
Not enough non-option arguments: got 1, need at least 2
Hay mucho más para yargs
que solo esto, así que consulte el archivo Léame para obtener más información.
Xa twenty
, tomaremos algunos argumentos opcionales, como una dirección IP y algunas banderas. Por ahora, usaremos yargs
Me gusta esto:
var argv = require('yargs')
.alias('d', 'distance')
.alias('j', 'json')
.alias('i', 'info')
.usage('Usage: $0 [options]')
.example('$0 -d 8.8.8.8', 'find the distance (km) between you and Google DNS')
.describe('d', 'Get distance between IP addresses')
.describe('j', 'Print location data as JSON')
.describe('i', 'Print location data in human readable form')
.help('h')
.alias('h', 'help')
.argv;
Dado que ninguno de nuestros argumentos es obligatorio, no utilizaremos .demand()
, pero usamos .alias()
, que dice yargs
que el usuario puede utilizar la forma corta o larga de cada bandera. También hemos agregado documentación de ayuda para mostrarle al usuario cuándo la necesita.
Estructurando la aplicación
Ahora que podemos obtener la entrada del usuario, ¿cómo tomamos esa entrada y la traducimos a un comando con los argumentos opcionales? Existen algunos módulos diseñados para ayudarlo a hacer esto, que incluyen:
- Plancha con el Complemento CLI
- Comandante
- Vorpal
Con muchos de estos marcos, el análisis de argumentos se realiza realmente por usted, por lo que ni siquiera necesita usar yargs
. Y en commander
es el caso, la mayor parte de su funcionalidad se parece mucho yargs
, aunque proporciona formas de enrutar comandos a funciones.
Dado que nuestra aplicación es bastante simple, nos limitaremos a usar yargs
Por ahora.
Añadiendo el código
No pasaremos demasiado tiempo aquí, ya que es específico solo para nuestra aplicación CLI, pero aquí está el código específico para nuestra aplicación:
var dns = require('dns');
var request = require('request');
var ipRegex = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/;
var toRad = function(num) {
return num * (Math.PI / 180);
};
var getIpInfo = function(server, callback) {
var ipinfo = function(p, cb) {
request('http://ipinfo.io/' + p, function(err, response, body) {
var json = JSON.parse(body);
cb(err, json);
});
};
if (!server) {
return ipinfo('json', callback);
} else if (!server.match(ipRegex)) {
return dns.lookup(server, function(err, data) {
ipinfo(data, callback);
});
} else {
return ipinfo(server, callback);
}
};
var ipDistance = function(lat1, lon1, lat2, lon2) {
// Earth radius in km
var r = 6371;
var dLat = toRad(lat2 - lat1);
var dLon = toRad(lon2 - lon1);
lat1 = toRad(lat1);
lat2 = toRad(lat2);
var a = Math.sin(dLat / 2.0) * Math.sin(dLat / 2.0) +
Math.sin(dLon / 2.0) * Math.sin(dLon / 2.0) * Math.cos(lat1) * Math.cos(lat2);
var c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));
return r * c;
};
var findLocation = function(server, callback) {
getIpInfo(server, function(err, data) {
callback(null, data.city + ', ' + data.region);
});
};
var findDistance = function(ip1, ip2, callback) {
var lat1, lon1, lat2, lon2;
getIpInfo(ip1, function(err, data1) {
var coords1 = data1.loc.split(',');
lat1 = Number(coords1[0]);
lon1 = Number(coords1[1]);
getIpInfo(ip2, function(err, data2) {
var coords2 = data2.loc.split(',');
lat2 = Number(coords2[0]);
lon2 = Number(coords2[1]);
var dist = ipDistance(lat1, lon1, lat2, lon2);
callback(null, dist);
});
});
};
Para el código fuente completo, puede encontrar el repositorio aquí.
Te puede interesar:¿Qué es Arduino?Lo único que nos queda por hacer con el código es conectar los argumentos de la CLI con el código de la aplicación anterior. Para que sea más fácil, pondremos todo esto en una función llamada cli()
, que usaremos más adelante.
Encapsulando el análisis de argumentos y la asignación de comandos dentro cli()
ayuda a mantener el código de la aplicación por separado, lo que permite que este código se importe como una biblioteca con require()
.
var cli = function() {
var argv = require('yargs')
.alias('d', 'distance')
.alias('j', 'json')
.alias('i', 'info')
.usage('Usage: $0 [IP | URL] [--d=IP | URL] [-ij]')
.example('$0 -d 8.8.8.8', 'find the distance (km) between you and Google DNS')
.describe('d', 'Get distance between IP addresses')
.describe('j', 'Print location data as JSON')
.describe('i', 'Print location data in human readable form')
.help('h')
.alias('h', 'help')
.argv;
var path="json";
if (argv._[0]) {
path = argv._[0];
}
if (argv.d) {
findDistance(path, argv.d, function(err, distance) {
console.log(distance);
});
} else if (argv.j) {
getIpInfo(path, function(err, data) {
console.log(JSON.stringify(data, null, 4));
});
} else if (argv.i) {
getIpInfo(path, function(err, data) {
console.log('IP:', data.ip);
console.log('Hostname:', data.hostname);
console.log('City:', data.city);
console.log('Region:', data.region);
console.log('Postal:', data.postal);
console.log('Country:', data.country);
console.log('Coordinates:', data.loc);
console.log('ISP:', data.org);
});
} else {
findLocation(path, function(err, location) {
console.log(location);
});
}
};
exports.info = getIpInfo;
exports.location = findLocation;
exports.distance = findDistance;
exports.cli = cli;
Aquí puede ver que básicamente solo usamos if...else
declaraciones para determinar qué comando ejecutar. Podría ser mucho más elegante y usar Flatiron para asignar cadenas de expresiones regulares a comandos, pero eso es un poco exagerado para lo que estamos haciendo aquí.
Haciéndolo ejecutable
Para que podamos ejecutar la aplicación, necesitamos especificar algunas cosas en nuestro package.json
archivo, como donde reside el ejecutable. Pero primero, creemos el ejecutable y su código. Crea un archivo llamado twenty
en el directorio twenty/bin/
y agréguele esto:
#!/usr/bin/env node
require('../index').cli();
El shebang#!/usr/bin/env node
) le dice a Unix cómo ejecutar el archivo, permitiéndonos omitir el node
prefijo. La segunda línea simplemente carga el código desde arriba y llama al cli()
función.
En package.json
, agregue el siguiente JSON:
"bin": {
"twenty": "./bin/twenty"
}
Esto solo le dice a npm dónde encontrar el ejecutable al instalar el paquete con el -g
bandera (global).
Así que ahora, si instala twenty
como global …
$ npm install -g twenty
… luego puede obtener las ubicaciones de los servidores y las direcciones IP:
$ twenty 198.41.209.141 #reddit
San Francisco, California
$ twenty rackspace.com
San Antonio, Texas
$ twenty usa.gov --j
{
"ip": "216.128.241.47",
"hostname": "No Hostname",
"city": "Phoenix",
"region": "Arizona",
"country": "US",
"loc": "33.3413,-112.0598",
"org": "AS40289 CGI TECHNOLOGIES AND SOLUTIONS INC.",
"postal": "85044"
}
$ twenty Pharos.sh.com
Ashburn, Virginia
Y ahí lo tiene, el servidor de abuso de pila se encuentra en Asburn, Virginia. Interesante =)
Para obtener el código fuente completo, consulte el proyecto en Github.
Te puede interesar:Cómo escribir middleware Express.js