Одна из моих самых любимых вещей в Node - это то, насколько легко создавать простые инструменты интерфейса командной строки (CLI). Между синтаксическим анализом аргументов с помощью yargs и управлением инструментами с помощью npm Node просто упрощает задачу.
Вот несколько примеров инструментов, о которых я говорю:
После установки (с параметром -g
) эти пакеты можно запускать из любой
точки командной строки и работать так же, как встроенные инструменты
Unix.
В последнее время я создал несколько приложений Node.js для командной строки и подумал, что было бы полезно написать об этом сообщение, которое поможет вам начать работу. Поэтому в этой статье я покажу вам, как создать инструмент командной строки для получения данных о местоположении для IP-адресов и URL-адресов.
Если вы видели статью Stack Abuse об изучении
Node.js , вы,
возможно, помните, что мы создали пакет под названием twenty
который
имел аналогичную функциональность. Мы будем развивать этот проект и
превращать его в подходящий инструмент CLI с большей функциональностью.
Настройка проекта
Начнем с создания нового каталога и настройки проекта с помощью npm:
$ mkdir twenty
$ npm init
Нажмите Enter для всех запросов в последней команде, и у вас должен быть
файл package.json
Обратите внимание, что, поскольку я уже взял имя пакета twenty
на
npm, вам придется переименовать его во что-то другое, если вы
действительно хотите опубликовать. Или вы также можете
охватить свой проект.
Затем создайте файл index.js
$ touch index.js
Это все, что нам действительно нужно для начала работы, и мы будем добавлять в проект по мере продвижения.
Разбор аргументов
Большинство приложений CLI принимают аргументы от пользователя, что является наиболее распространенным способом ввода данных. В большинстве случаев разобрать аргументы не так уж сложно, поскольку обычно используется лишь несколько команд и флагов. Но по мере того, как инструмент становится более сложным, будет добавляться больше флагов и команд, а синтаксический анализ аргументов может стать на удивление трудным.
Чтобы помочь нам в этом, мы будем использовать пакет под названием
yargs
, который является
преемником популярного пакета
optimist.
yargs
был создан, чтобы помочь вам анализировать команды пользователя,
например:
var argv = require('yargs').argv;
Теперь можно легко получить доступ к сложным optstrings, таким как
node index.js install -v --a=22 -cde -x derp
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
даже поможет вам с указанием командного интерфейса, поэтому,
если ввод пользователя не соответствует определенным требованиям, он
покажет ему сообщение об ошибке. Так, например, мы можем сказать yargs
нам нужны как минимум 2 аргумента:
var argv = require('yargs')
.demand(2)
.argv
И если пользователь не предоставит хотя бы два, он увидит это сообщение об ошибке по умолчанию:
$ node index.js foo
Not enough non-option arguments: got 1, need at least 2
yargs
- это гораздо больше, чем просто это, поэтому ознакомьтесь с
readme для получения дополнительной информации.
Для twenty
мы будем принимать несколько необязательных аргументов,
таких как IP-адрес и некоторые флаги. На данный момент мы будем
использовать такие yargs
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;
Поскольку ни один из наших аргументов не требуется, мы не будем
использовать .demand()
, но мы используем .alias()
, который
сообщает yargs
что пользователь может использовать краткую или длинную
форму каждого флага. Мы также добавили некоторую справочную
документацию, чтобы показать пользователю, когда она ему понадобится.
Структурирование приложения
Теперь, когда мы можем получить ввод от пользователя, как нам взять этот ввод и преобразовать его в команду с необязательными аргументами? Есть несколько модулей, которые помогут вам в этом, в том числе:
Во многих из этих фреймворков синтаксический анализ аргументов
фактически выполняется за вас, поэтому вам даже не нужно использовать
yargs
. А в commander
большая часть его функциональных возможностей
очень похожа на yargs
, хотя он предоставляет способы маршрутизации
команд к функциям.
Поскольку наше приложение довольно простое, мы пока будем использовать
yargs
.
Добавление кода
Мы не будем тратить здесь много времени, поскольку это относится только к нашему приложению CLI, но вот код, специфичный для нашего приложения:
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);
});
});
};
Полный исходный код вы можете найти здесь .
Единственное, что нам осталось сделать с кодом, - это связать аргументы
CLI с приведенным выше кодом приложения. Чтобы упростить задачу, мы
поместим все это в функцию cli()
, которую мы будем использовать
позже.
Инкапсуляция синтаксического анализа аргументов и сопоставления команд в
cli()
помогает разделить код приложения, что позволяет импортировать
этот код как библиотеку с помощью 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;
Здесь вы можете видеть, что мы в основном просто используем if...else
чтобы определить, какую команду запускать. Вы могли бы стать намного
интереснее и использовать Flatiron для сопоставления строк регулярных
выражений с командами, но это немного излишне для того, что мы здесь
делаем.
Делаем его исполняемым
Чтобы мы могли запустить приложение, нам нужно указать несколько вещей в
нашем package.json
, например, где находится исполняемый файл. Но
сначала давайте создадим исполняемый файл и его код. Создайте файл с
именем twenty
в каталоге twenty/bin/
и добавьте к нему:
#!/usr/bin/env node
require('../index').cli();
Shebang ( #!/usr/bin/env node
) сообщает Unix, как выполнять файл,
позволяя нам не node
префикс узла. Вторая строка просто загружает код
сверху и вызывает функцию cli()
В package.json
добавьте следующий JSON:
"bin": {
"twenty": "./bin/twenty"
}
Это просто сообщает npm, где найти исполняемый файл при установке пакета
с -g
(global).
Итак, теперь, если вы установите twenty
как глобальный ...
$ npm install -g twenty
... затем вы можете получить расположение серверов и 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 stackabuse.com
Ashburn, Virginia
И вот, сервер Stack Abuse расположен в Асберне, штат Вирджиния. Интересно =)
Полный исходный код можно найти в проекте на Github .