Skip to content

Commit

Permalink
CLI o'clock
Browse files Browse the repository at this point in the history
  • Loading branch information
rjz committed Jan 27, 2014
1 parent 28de70f commit d570594
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 130 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.swp
node_modules/
npm-debug.log
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
# Product name generator
# Codename generator

Generates random names for product releases
Generates codenames. What for, you ask?

### Installation

$ npm install -g codename

### Usage

$ codename \
--lists=crayons,adjectives,cities,animals \
--filters=alliterative,random

### CLI Options

* `lists` - a comma-delimited list of the lists to be sourced for
name generation

* `filters` - a comma-delimited list of the filters to be applied

### Word Lists

Expand Down
15 changes: 15 additions & 0 deletions bin/codename
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node

var _ = require('lodash');

var argv = require('optimist')
.default('lists', 'adjectives,crayons,animals')
.default('filters', 'alliterative,random').argv;

var filters = argv.filters.split(','),
listNames = argv.lists.split(',');

var result = require('../index')().generate(filters, listNames);

process.stdout.write(result.join(' ') + '\n');

56 changes: 17 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,32 @@
var _ = require('underscore'),
fs = require('fs'),
var _ = require('lodash'),
path = require('path');

var Lists = require(path.resolve(__dirname, 'src/lists')),
Words = require(path.resolve(__dirname, 'src/words'));
var filters = require(path.resolve('./src', 'filters')),
generate = require(path.resolve('./src', 'generate')),
loadLists = require(path.resolve('./src', 'load'));

function pickFilename (stat, file) {
if (stat.isFile()) return file;
function getFunction (obj, key) {
if (_.isFunction(key)) return key;
return obj[key];
}

function toKey (file) {
return path.basename(file, path.extname(file));
}

function toWords (buf) {
return new Words(_.compact(buf.toString().split('\n')).map(capitalish).sort());
}

function readFile (f) {
return fs.readFileSync(f);
}

function capitalish (str) {
return str[0].toUpperCase() + str.slice(1);
}

// List all real files in the directory
function getDirectoryFilesSync (dir) {

var fqpath = function (file) {
return path.join(dir, file);
};

var files = fs.readdirSync(dir).map(fqpath),
stats = files.map(fs.lstatSync);

return _.compact(_.map(_.object(files, stats), pickFilename));
}

module.exports = function (opts) {;
module.exports = function (opts) {

var defaults = {
dir: path.resolve(__dirname, 'resources')
};

var options = _.extend({}, defaults, opts);

var results = getDirectoryFilesSync(options.dir),
lists = results.map(readFile);
var lists = loadLists(options.dir);

return new Lists(_.object(results.map(toKey), lists.map(toWords)));
var getList = _.partial(getFunction, lists),
getFilter = _.partial(getFunction, filters);

return {
generate: function (filters, listNames) {
return generate(listNames.map(getList), filters.map(getFilter));
}
};
};

16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
{
"name": "product-name-generator",
"name": "codename",
"version": "0.0.1",
"description": "Generate random names for products",
"description": "Codename generator",
"main": "index.js",
"bin": {
"codename": "./bin/codename"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "./node_modules/jshint/bin/jshint **/*.js && ./node_modules/jasmine-node/bin/jasmine-node test"
},
"author": "RJ Zaworski <[email protected]>",
"license": "WTFPL",
"dependencies": {
"async": "~0.2.9",
"underscore": "~1.5.2"
"lodash": "~2.4.1",
"optimist": "0.3.7"
},
"devDependencies": {
"jshint": "~2.4.1",
"jasmine-node": "~1.12.0"
}
}
27 changes: 27 additions & 0 deletions src/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
var _ = require('lodash');

var exports = module.exports = {

// A filter for ensuring that the first `letter` of the word
first: function (letter, memo, words) {
if (_.isString(letter) && letter.length == 1) {
return exports.alliterative([letter.toUpperCase()], words);
}

throw new Error('`first` must be called with a single letter');
},

// A filter for ensuring that all `words` share their first letter
alliterative: function (memo, words) {
if (!memo.length) return words;
return words.filter(function (b) {
return b[0] == memo[0][0];
});
},

// A filter for returning a random item from `words`
random: function (memo, words) {
return _.sample(words);
}
};

48 changes: 48 additions & 0 deletions src/generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
var _ = require('lodash');

// Comparator for sorting `[[key, val]]` pairs by the shortest `val`
function pairsByShortest (a, b) {
return a[1].length > b[1].length;
}

// Returns `[[keys], [lists]]` sorted in order of grouping constraints;
function byPlausibility (lists) {
var pairs = _.map(_.pairs(lists)).sort(pairsByShortest);
return _.zip.apply(_, pairs);
}

/**
* Generate a name by applying `filters` to each of `lists`
*
* params:
*
* - `lists` (`Array[String]`) - an array of word lists
* - `filters` (`Array[Function]`) - an array of filter functions to be
* applied to each list
*
* return:
*
* `Array[String]` containing the generated name or `null` if filters
* eliminated all possible matches
*/
module.exports = function generate (lists, filters) {

var getWordById, results, words,
kvs = byPlausibility(lists);

words = _.compact(kvs[1].reduce(function (memo, list) {
return memo.concat(filters.reduce(function (m, f) {
return f(memo, m);
}, list));
}, []));

if (words.length < lists.length - 1) {
return null;
}

results = _.object(kvs[0], words);
getWordById = _.partial(_.result, results);

return _.keys(results).sort().map(getWordById);
};

59 changes: 0 additions & 59 deletions src/lists.js

This file was deleted.

48 changes: 48 additions & 0 deletions src/load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
var _ = require('lodash'),
fs = require('fs'),
path = require('path');

function pickFilename (stat, file) {
if (stat.isFile()) return file;
}

function toKey (file) {
return path.basename(file, path.extname(file));
}

function toNormalizedWords (buf) {
return _.compact(buf.toString().split('\n')).map(capitalish).sort();
}

function readFile (f) {
return fs.readFileSync(f);
}

function capitalish (str) {
return str[0].toUpperCase() + str.slice(1);
}

// List all "real" files in the directory
function getDirectoryFilesSync (dir) {

var fqpath = function (file) {
return path.join(dir, file);
};

var files = fs.readdirSync(dir).map(fqpath),
stats = files.map(fs.lstatSync);

return _.compact(_.map(_.object(files, stats), pickFilename));
}

/**
* Loads and normalizes wordlists from the given `dir`
*/
module.exports = function loadLists (dir) {
var results = getDirectoryFilesSync(dir),
listNames = results.map(toKey),
listContents = results.map(readFile).map(toNormalizedWords);

return _.object(listNames, listContents);
};

26 changes: 0 additions & 26 deletions src/words.js

This file was deleted.

8 changes: 8 additions & 0 deletions test/words_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var words = require('../src/words');

describe('Words', function () {
it('exists', function () {
expect(words).toBeDefined();
});
});

0 comments on commit d570594

Please sign in to comment.