/**
* Command-line interface.
*
* @module cli
*/
/*jshint boss:true, laxbreak:true*/
function resourceListToString(resourceList, short) {
var nK = resourceList.length,
result = ["\nAvailable resources (", nK, "):\n"],
sIndent = " ",
nI, resource;
for (nI = 0; nI < nK; nI++) {
resource = resourceList[nI];
if (short) {
result.push("\n",
sIndent, "* ", resource.name, " (", resource.url, ") - ", resource.description);
}
else {
result.push("\n",
sIndent, resource.name, " - ", resource.description, "\n",
sIndent, "url: ", resource.url, "\n",
sIndent, "tags: ", resource.tag.join(" "), "\n");
if (resource.note) {
result.push(sIndent, resource.note, "\n");
}
}
}
return result.join("");
}
var fs = require("fs"),
charm = require("charm")(),
nomnom = require("nomnom"),
formatUnit = require("./format"),
resourceUnit = require("./resource"),
seeq = require("./seeq"),
pkg = JSON.parse(fs.readFileSync(__dirname + "/../package.json", "utf8")),
resourceList = resourceUnit.getList({includeApi: true}),
generalSettingName = {
"partialMatch": null,
"caseSensitive": null,
"search": null,
"limit": null
},
argParser = nomnom()
.script(pkg.name)
.options({
"name": {
position: 0,
list: true,
help: "Name/string that should be searched for or checked for presence on a resource"
},
"help": {
abbr: "h",
help: "Show usage information and exit"
},
"listResource": {
abbr: "l",
full: "list-resource",
flag: true,
help: "Show information about all available resources"
},
"resource": {
abbr: "r",
full: "at",
help: "Filter for available resources by name: comma-separated list of names (case-insensitive) of resources that should be checked/searched"
},
"resourceTag": {
abbr: "t",
full: "tag",
help: "Filter for available resources by tag: comma-separated list of tags (case-insensitive); -tag means that resource should not have the tag"
},
"checkAllTags": {
full: "all-tag",
flag: true,
help: "Whether all specified tags should be checked for a resource"
},
"partialMatch": {
abbr: "p",
full: "partial-match",
help: "Allow partial matching when checking name: 0 - disallow (by default), 1 - allow at the beginning of matching strings, 2 - allow substring matching",
choices: [0, 1, 2]
},
"caseSensitive": {
abbr: "c",
full: "case-sensitive",
flag: true,
help: "Use case-sensitive check/search when possible"
},
"search": {
abbr: "s",
flag: true,
help: "Make search instead of check"
},
"limit": {
abbr: "m",
help: "Limit of quantity of results per resource",
callback: function(value) {
if (value < 1) {
return "Limit should be equal to or greater than 1";
}
}
},
"format": {
abbr: "f",
"default": formatUnit.exists("text") ? "text" : "raw",
help: "Result format; can be: " + formatUnit.getNameList().join(", ")
},
"verbose": {
abbr: "V",
flag: true,
help: "Enable verbose output"
},
"version": {
abbr: "v",
flag: true,
help: "Show program version"
}
})
.help(resourceListToString(resourceList, true));
(function() {
var nI, nL, settings, sName, value;
// Add options of resources
for (nI = 0, nL = resourceList.length; nI < nL; nI++) {
value = resourceList[nI];
if (settings = value.api.settings) {
sName = value.id;
for (value in settings) {
argParser.option(sName + "-" + value, settings[value]);
}
}
}
}());
function showProgress(data) {
var sOut = [data.number, "/", data.total, " (", Math.floor(data.number * 100 / data.total), "%)"].join("");
charm.left(showProgress.shift);
charm.write(sOut);
showProgress.shift = sOut.length;
if (data.number === data.total) {
charm.write("\n");
}
}
showProgress.shift = 3;
function showResult(resultMap, nameList, args) {
var sFormat = args.format,
result = sFormat ? formatUnit.exists(sFormat) : false;
if (result) {
result = formatUnit.format(resultMap,
sFormat,
{
queryList: nameList,
verbose: args.verbose
});
}
else {
result = JSON.stringify(resultMap, null, 4);
}
console.log("\nResults:\n\n" + result);
}
/**
* Perform operation using specified options.
*
* @param {Object} [args]
* Operation options.
* @alias module:cli.run
*/
function run(args) {
var bShowUsage = true,
list,
nameList,
nL,
resourceNameList,
resourceSettings,
sName,
value;
if (! args) {
args = {};
}
if (args.version) {
console.log("Version: " + pkg.version);
bShowUsage = false;
}
if (args.listResource) {
console.log(resourceListToString(resourceList));
bShowUsage = false;
}
if (args.name && args.name.length) {
// List of names to check
nameList = args.name;
// Determine resources to check
if (args.resource) {
args.resource = args.resource.split(",");
}
if (args.resourceTag) {
args.resourceTag = args.resourceTag.split(",");
}
resourceNameList = resourceUnit.getNameList({
selectName: args.resource,
selectTag: args.resourceTag,
checkAllTags: args.checkAllTags
});
// Make check if resources are specified
if (resourceNameList.length) {
// Prepare resources settings
resourceSettings = {
};
for (sName in args) {
value = args[sName];
// General settings
if (sName in generalSettingName) {
(resourceSettings._general || (resourceSettings._general = {}))[sName] = value;
}
// Resource-specific settings
else if (list = /^(\w+)\-(.+)$/.exec(sName)) {
if (sName = resourceUnit.getIdByName(list[1])) {
( resourceSettings[sName] || (resourceSettings[sName] = {}) )[ list[2] ] = value;
}
}
}
// Start checking
console.log("Checking " + resourceNameList.join(", ") + "...");
if (nameList.length > 1 || resourceNameList.length > 1) {
value = showProgress;
charm.pipe(process.stdout);
charm.write("Progress: 0 %");
}
else {
value = null;
}
seeq.search(nameList,
function(resultMap) {
showResult(resultMap, nameList, args);
},
{
resource: resourceNameList,
settings: resourceSettings,
progressCallback: value
});
}
// If no resource is found
else {
console.log("No resource is found.");
if (args.resource) {
list = args.resource.filter(function(name) {
return ! resourceUnit.isAvailable(name);
});
if (nL = list.length) {
console.log(nL > 1
? "The following resources are unknown: " + list.join(", ")
: list[0] + " is unknown resource");
}
}
console.log("Run `" + pkg.name + " -l` to see information about all available resources.");
}
}
else if (bShowUsage) {
argParser.print(argParser.getUsage());
}
}
/**
* Perform operation specified via command-line arguments.
*
* @param {Object} [options]
* Predefined values for command-line options.
* Each option specified in this object will be used only when the corresponding option is not set in command-line arguments.
* @alias module:cli.cli
* @see {@link module:cli.run run}
*/
function cli(options) {
var args = argParser.parse(),
optionMap,
sName,
value;
// Use predefined values for absent arguments
if (options) {
optionMap = argParser.specs;
for (sName in optionMap) {
if (! (sName in args)) {
if (sName in options) {
args[sName] = options[sName];
}
else {
value = optionMap[sName];
if (value.full in options) {
args[sName] = options[value.full];
}
else if (value.abbr in options) {
args[sName] = options[value.abbr];
}
}
}
}
}
// Run CLI
run(args);
}
// Exports
exports.cli = cli;
exports.run = run;
// If script is run directly from Node
if (require.main === module) {
cli();
}