This repository has been archived by the owner on Jun 15, 2021. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit, working search command
- Loading branch information
0 parents
commit 0ec60bf
Showing
20 changed files
with
952 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1 @@ | |||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,3 @@ | |||
#!/usr/bin/env node | |||
|
|||
require('../lib/main').exec(process.argv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,46 @@ | |||
function UnknownMainError (message) { | |||
Error.call(this) | |||
Error.captureStackTrace(this, arguments.callee) | |||
this.message = message | |||
this.name = 'UnknownMainError' | |||
} | |||
UnknownMainError.prototype.__proto__ = Error.prototype | |||
|
|||
var nopt = require('nopt') | |||
|
|||
, knownOptions = { | |||
'output': String | |||
, 'use': String | |||
, 'max': Number | |||
, 'sandbox': Array | |||
, 'noop': Boolean | |||
, 'silent': Boolean | |||
, 'help': Boolean | |||
, 'sans': Boolean | |||
, 'debug': Boolean | |||
} | |||
|
|||
, shorthandOptions = { | |||
'o': 'output' | |||
, 'u': 'use' | |||
, 'x': 'noop' | |||
, 's': 'silent' | |||
} | |||
|
|||
, knownMains = [ 'help', 'build', 'refresh', 'info', 'search', 'compile' ] | |||
|
|||
, parse = function (argv) { | |||
var parsed = nopt(knownOptions, shorthandOptions, argv) | |||
, main = parsed.argv.remain[0] | |||
, remaining = parsed.argv.remain.slice(1) | |||
|
|||
if (knownMains.indexOf(main) == -1) | |||
throw new UnknownMainError('Unknown main command "' + main + '"') | |||
|
|||
return { | |||
main: main | |||
, remaining: remaining | |||
} | |||
} | |||
|
|||
module.exports.parse = parse |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,79 @@ | |||
var extend = require('./util').extend | |||
, searchUtil = require('./main-search-util') | |||
, Output = require('./output') | |||
|
|||
, SearchOutput = extend(Output, { // inherit from Output | |||
|
|||
test: function () { | |||
console.log('test', this.outfd, this.debug) | |||
} | |||
|
|||
, searchInit: function () { | |||
this.statusMsg('Searching NPM registry...') | |||
this.log() | |||
} | |||
|
|||
, searchNoResults: function () { | |||
this.warnMsg('Sorry, we couldn\'t find anything. :(') | |||
} | |||
|
|||
, searchError: function (err) { | |||
this.repositoryError(err, 'Something went wrong searching NPM') | |||
} | |||
|
|||
, searchResults: function (results) { | |||
if (results.primary) { | |||
this.heading('Ender tagged results:') | |||
results.primary.forEach(function (item) { | |||
this.processItem(item, results.terms) | |||
}.bind(this)) | |||
} | |||
|
|||
if (results.secondary) { | |||
var meta = results.secondaryTotal > results.secondary.length | |||
? results.secondary.length + ' of ' + results.secondaryTotal | |||
: '' | |||
this.heading('NPM general results:', meta) | |||
results.secondary.forEach(function (item) { | |||
this.processItem(item, results.terms) | |||
}.bind(this)) | |||
} | |||
} | |||
|
|||
, processItem: function (item, terms) { | |||
var reg = new RegExp('(' + terms.map(function (item) { | |||
return searchUtil.escapeRegExp(item) | |||
}).join('|') + ')', 'ig') | |||
, maintainers = '' | |||
, title = item.name | |||
, last | |||
|
|||
if (item.description) | |||
title += ' - ' + item.description.substring(0, 80) + (item.description.length > 80 ? '...' : '') | |||
|
|||
this.log('+ ' + title.replace(reg, '$1'.cyan)) | |||
|
|||
if (item.maintainers && item.maintainers.length) { | |||
item.maintainers = item.maintainers.map(function (maintainer) { | |||
return maintainer.replace(/^=/, '@') | |||
}) | |||
|
|||
if (item.maintainers.length > 1) { | |||
last = item.maintainers.splice(-1)[0] | |||
maintainers = item.maintainers.join(', ') | |||
maintainers += ' & ' + last | |||
} else { | |||
maintainers = item.maintainers[0] | |||
} | |||
} | |||
|
|||
this.log(' by ' + maintainers.replace(reg, '$1'.cyan) + '\n') | |||
} | |||
|
|||
, create: function (outfd, debug) { | |||
return Object.create(this).init(outfd, debug) | |||
} | |||
|
|||
}) | |||
|
|||
module.exports = SearchOutput |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,38 @@ | |||
// make a string regex friendly by escaping regex characters | |||
// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License | |||
var escapeRegExp = function (string) { | |||
return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function (match) { | |||
return '\\' + match | |||
}) | |||
} | |||
|
|||
// given a regex, an array of objects and an array of properties prioritised, populate a ranked | |||
// array with objects whose properties match the regex in priority order. elements get removed | |||
// from source array when they are put into the priority array. | |||
// array: [ { a: '1;, b: '2' }, { a: '3', b: '4' } ] | |||
// ranked: [] (effective return) | |||
// priority: [ 'a', 'b' ] (properties in 'array' elements) | |||
, sortByRegExp = function (regex, array, ranked, priority) { | |||
for (var i = 0; i < priority.length; i++) { | |||
var p = priority[i] | |||
for (var j = 0; j < array.length; j++) { | |||
if (typeof array[j][p] == 'string' && regex.test(array[j][p])) { | |||
ranked.push(array.splice(j, 1)[0]) | |||
j-- | |||
} else if (array[j][p] && typeof array[j][p] != 'string') { | |||
for (var m = 0; m < array[j][p].length; m++) { | |||
if (regex.test(array[j][p][m])) { | |||
ranked.push(array.splice(j, 1)[0]) | |||
j-- | |||
break | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
|
|||
module.exports = { | |||
escapeRegExp: escapeRegExp | |||
, sortByRegExp: sortByRegExp | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,77 @@ | |||
var repository = require('./repository') | |||
, escapeRegExp = require('./main-search-util').escapeRegExp | |||
, sortByRegExp = require('./main-search-util').sortByRegExp | |||
, defaultMax = 8 | |||
|
|||
, exec = function (args, out) { | |||
var terms = args.remaining | |||
, max = args.max || defaultMax | |||
, handler = handle.bind(null, terms, max, out) | |||
|
|||
out && out.searchInit() | |||
|
|||
repository.setup(function (err) { | |||
if (err) | |||
return out && out.repositoryLoadError(err) | |||
|
|||
repository.search(terms, handler) | |||
}) | |||
} | |||
|
|||
, handle = function (terms, max, out, err, data) { | |||
var primary = [] | |||
, secondary = [] | |||
|
|||
if (err) | |||
return out && out.searchError(err) | |||
|
|||
repository.packup() | |||
|
|||
if (data) { | |||
Object.keys(data).forEach(function (id) { | |||
var d = data[id] | |||
if (d.keywords) | |||
(d.keywords.indexOf('ender') == -1 ? secondary : primary).push(d) | |||
}) | |||
} | |||
|
|||
if (!primary.length && !secondary.length) | |||
return out && out.searchNoResults() | |||
|
|||
if (primary.length) | |||
primary = rankRelevance(terms, primary) | |||
|
|||
out && out.searchResults({ | |||
terms: terms | |||
, max: max | |||
, primary: primary.length ? primary : null | |||
, secondaryTotal: secondary.length | |||
, secondary: secondary.length && (max - primary.length > 0) | |||
? rankRelevance(terms, secondary).slice(0, max - primary.length) | |||
: null | |||
}) | |||
} | |||
|
|||
, rankRelevance = function (args, data) { | |||
var sorted = [] | |||
, priority = [ 'name', 'keywords', 'description' ] | |||
, args = args.map(function (arg) { return escapeRegExp(arg) }) | |||
, regex | |||
|
|||
// args as exact phrase for name | |||
regexp = new RegExp('^' + args.join('\\s') + '$') | |||
sortByRegExp(regexp, data, sorted, [ 'name' ]) | |||
|
|||
// args as phrase anywhere | |||
regexp = new RegExp('\\b' + args.join('\\s') + '\\b', 'i') | |||
sortByRegExp(regexp, data, sorted, priority) | |||
|
|||
// args as keywords anywhere (ex: useful for case when express matches expresso) | |||
regexp = new RegExp('\\b' + args.join('\\b\|\\b') + '\\b', 'i') | |||
sortByRegExp(regexp, data, sorted, priority) | |||
|
|||
// we don't really care about relevance at this point :P | |||
return sorted.concat(data) | |||
} | |||
|
|||
module.exports.exec = exec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,11 @@ | |||
var argsParse = require('./args-parse') | |||
|
|||
, exec = function (argv) { | |||
var args = argsParse.parse(argv) | |||
, exe = args && require('./main-' + args.main) | |||
, out = args && require('./main-' + args.main + '-output').create(require('util')) | |||
|
|||
exe && exe.exec(args, out) | |||
} | |||
|
|||
module.exports.exec = exec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,56 @@ | |||
var colors = require('colors') | |||
|
|||
, Output = { | |||
|
|||
init: function (out, isDebug) { | |||
this.out = out // an object with a 'print' method, like `require('util')` | |||
this.isDebug = isDebug | |||
return this | |||
} | |||
|
|||
, print: function (string) { | |||
this.out && this.out.print(string) | |||
} | |||
|
|||
// generic method, like console.log, should avoid in favour of more specific 'views' | |||
, log: function (string) { | |||
if (typeof string != 'undefined') | |||
this.print(string) | |||
this.print('\n') | |||
} | |||
|
|||
, debug: function (string) { | |||
this.isDebug && this.print('DEBUG: ' + String(string) + '\n') | |||
} | |||
|
|||
, statusMsg: function (string) { | |||
this.log(string.grey) | |||
} | |||
|
|||
, warnMsg: function (string) { | |||
this.log(string.grey) | |||
} | |||
|
|||
, repositoryError: function (err, msg) { | |||
if (this.isDebug) | |||
throw err | |||
|
|||
this.log(msg.red) | |||
} | |||
|
|||
, repositoryLoadError: function (err) { | |||
this.repositoryError(err, 'Something went wrong trying to load NPM!') | |||
} | |||
|
|||
, heading: function (string, meta) { | |||
this.log(string.yellow + (meta ? (' (' + meta + ')').grey : '')) | |||
this.log(string.replace(/./g, '-')) | |||
} | |||
|
|||
, create: function () { | |||
return Object.create(this) | |||
} | |||
|
|||
} | |||
|
|||
module.exports = Output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,77 @@ | |||
function RepositorySetupError (message) { | |||
Error.call(this) | |||
Error.captureStackTrace(this, arguments.callee) | |||
this.message = message | |||
this.name = 'RepositorySetupError' | |||
} | |||
RepositorySetupError.prototype.__proto__ = Error.prototype | |||
|
|||
var npm = require('npm') | |||
, fs = require('fs') | |||
, net = require('net') | |||
, colors = require('colors') | |||
, util = require('./util') | |||
|
|||
, isSetup = false | |||
, sessionFile | |||
, sessionStream | |||
|
|||
, generateTempFile = function (callback) { | |||
sessionFile = util.tmpDir + '/ender_npm_' + process.pid + '.' + (+new Date()) | |||
fs.open(sessionFile, 'w', '0644', callback) | |||
} | |||
|
|||
// must be called at the start of an NPM session | |||
, setup = function (callback) { | |||
if (isSetup) | |||
return callback && callback() | |||
|
|||
generateTempFile(function (err, fd) { | |||
if (err) | |||
return callback(err) | |||
|
|||
sessionStream = new net.Stream(fd) | |||
var config = { | |||
logfd: sessionStream | |||
, outfd: sessionStream | |||
} | |||
|
|||
npm.load(config, function (err) { | |||
if (!err) | |||
isSetup = true | |||
|
|||
callback.apply(null, arguments) | |||
}) | |||
}) | |||
} | |||
|
|||
// must be called at the end of an NPM session | |||
, packup = function (wasError, callback) { | |||
if (!isSetup) | |||
return callback && callback() | |||
|
|||
isSetup = false | |||
|
|||
sessionStream.on('close', function () { | |||
if (!wasError) | |||
return fs.unlink(sessionFile, callback) | |||
|
|||
callback && callback() | |||
sessionStream = null | |||
}) | |||
|
|||
sessionStream.destroySoon() | |||
} | |||
|
|||
, search = function (keywords, callback) { | |||
if (!isSetup) | |||
throw new RepositorySetupError('repository.setup() has not been called') | |||
|
|||
npm.commands.search(keywords, callback) | |||
} | |||
|
|||
module.exports = { | |||
search: search | |||
, setup: setup | |||
, packup: packup | |||
} |
Oops, something went wrong.