From dc9d24b26fd3fe2262fe2883fb67bfb02b27e0bc Mon Sep 17 00:00:00 2001 From: Mark Tucker Date: Mon, 20 Apr 2020 21:47:27 -0700 Subject: [PATCH] Add custom lists --- README.md | 44 ++++++++++++++++ index.js | 60 ++++++++++++++------- package-lock.json | 41 +++++++++++---- tests/main.js | 130 +++++++++++++++++++++++++++++----------------- 4 files changed, 198 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index c9dde29..0a2e01b 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,27 @@ Sentencer.configure({ // the list of adjectives to use. Again, Sentencer comes with one! adjectiveList: [], + // additional lists that generate actions for the template engine to use. + customLists: [ + { + // add action for animal + key: "animal", + values: ["dog", "cat", "elephant"], + // if named, add action for articlize + articlize: "an_animal", + // if named, add action for pluralize + pluralize: "animals" + }, + { + key: "band", + values: ["The Beatles", "The Who", "Styx"], + // no key or empty value, don't articlize + articlize: "", + // no key or empty value, don't pluralize + pluralize: "" + } + ], + // additional actions for the template engine to use. // you can also redefine the preset actions here if you need to. // See the "Add your own actions" section below. @@ -145,6 +166,29 @@ console.log( Sentencer.make("I can count to {{ number(8, 10) }}.") // "I can count to 8." ``` +### Add your own custom lists +When configuring `Sentencer` you can provide your own custom lists, which are converted to "actions". The `key` sets the name of the action and the `values` the list of values where one is selected when the action is called. You can also specify a name for the `articlize` and/or `pluralize` actions. These names are referenced within a sentence template. + +Here is an example of an animal list that includes options to prefix with an article or to make it plural. + +```javascript +var Sentencer = require('sentencer'); + +Sentencer.configure({ + customLists: [ + { + key: "animal", + values: ["dog", "cat", "elephant"], + articlize: "an_animal", + pluralize: "animals" + } + ], +}); + +console.log( Sentencer.make("I saw {{ an_animal }}, 1 {{ animal }}, and 2 {{ animals }}.") +// "I saw an elephant, 1 dog, and 2 cats." +``` + ### Where are the verbs? Verb pluralization, singularization, and tense modification are difficult computer science problems. `Sentencer` doesn't aim to solve those problems, however _present tense_ verb pluralization/singularization is an experimental feature of [`natural`](https://github.com/NaturalNode/natural) and could be integrated if necessary. diff --git a/index.js b/index.js index 79ed92e..0e2289d 100644 --- a/index.js +++ b/index.js @@ -11,36 +11,58 @@ var _ = require('lodash'); function Sentencer() { var self = this; - self._nouns = require('./words/nouns.js'); + self._nouns = require('./words/nouns.js'); self._adjectives = require('./words/adjectives.js'); self.actions = { - noun: function() { + noun: function () { return randy.choice(self._nouns); }, - a_noun: function() { - return articles.articlize( self.actions.noun() ); + a_noun: function () { + return articles.articlize(self.actions.noun()); }, - nouns: function() { - return nounInflector.pluralize( randy.choice(self._nouns) ); + nouns: function () { + return nounInflector.pluralize(randy.choice(self._nouns)); }, - adjective: function() { + adjective: function () { return randy.choice(self._adjectives); }, - an_adjective: function() { - return articles.articlize( self.actions.adjective() ); + an_adjective: function () { + return articles.articlize(self.actions.adjective()); } }; - self.configure = function(options) { + // function definitions + self._func_normal = function(values) { + return randy.choice(values); + } + self._func_articlize = function(name) { + return articles.articlize( self.actions[name]() ); + } + self._func_pluralize = function(values) { + return nounInflector.pluralize( randy.choice(values) ); + } + + self.configure = function (options) { // merge actions - self.actions = _.merge(self.actions, options.actions || {}); + self.actions = _.merge(self.actions, options.actions || {}); // overwrite nouns and adjectives if we got some - self._nouns = options.nounList || self._nouns; + self._nouns = options.nounList || self._nouns; self._adjectives = options.adjectiveList || self._adjectives; + self._customLists = options.customLists || []; + + self._customLists.forEach(item => { + self.actions[item.key] = self._func_normal.bind(null, item.values); + if (item.articlize) { + self.actions[item.articlize] = self._func_articlize.bind(null, item.key); + } + if (item.pluralize) { + self.actions[item.pluralize] = self._func_pluralize.bind(null, item.values); + } + }); }; - self.use = function(options) { + self.use = function (options) { var newInstance = new Sentencer(); newInstance.configure(options); return newInstance; @@ -51,19 +73,19 @@ function Sentencer() { // THE GOODS // --------------------------------------------- -Sentencer.prototype.make = function(template) { +Sentencer.prototype.make = function (template) { var self = this; var sentence = template; var occurrences = template.match(/\{\{(.+?)\}\}/g); - if(occurrences && occurrences.length) { - for(var i = 0; i < occurrences.length; i++) { + if (occurrences && occurrences.length) { + for (var i = 0; i < occurrences.length; i++) { var action = occurrences[i].replace('{{', '').replace('}}', '').trim(); var result = ''; var actionIsFunctionCall = action.match(/^\w+\((.+?)\)$/); - if(actionIsFunctionCall) { + if (actionIsFunctionCall) { var actionNameWithParens = action.match(/^(\w+)\(/); var actionName = actionNameWithParens[1]; var actionExists = self.actions[actionName]; @@ -75,10 +97,10 @@ Sentencer.prototype.make = function(template) { var args = _.map(actionContents.split(','), maybeCastToNumber); result = self.actions[actionName].apply(null, args); } - catch(e) { } + catch (e) { } } } else { - if(self.actions[action]) { + if (self.actions[action]) { result = self.actions[action](); } else { result = '{{ ' + action + ' }}'; diff --git a/package-lock.json b/package-lock.json index a0eb812..2aaf953 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1106,7 +1106,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1127,12 +1128,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1147,17 +1150,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1274,7 +1280,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1286,6 +1293,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1300,6 +1308,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1307,12 +1316,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -1331,6 +1342,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1411,7 +1423,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1423,6 +1436,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1508,7 +1522,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1544,6 +1559,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1563,6 +1579,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1606,12 +1623,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/tests/main.js b/tests/main.js index 0741485..3d6dcc0 100644 --- a/tests/main.js +++ b/tests/main.js @@ -2,58 +2,58 @@ var assert = require("assert"); var Sentencer = require('../index.js'); -describe('Sentencer:', function() { +describe('Sentencer:', function () { - it('should exist', function() { + it('should exist', function () { assert(Sentencer); }); - describe('Default', function() { + describe('Default', function () { - describe('# Words', function() { + describe('# Words', function () { - it('should include a list of nouns', function() { + it('should include a list of nouns', function () { assert(Sentencer._nouns.length); }); - it('should include a list of adjectives', function() { + it('should include a list of adjectives', function () { assert(Sentencer._adjectives.length); }); }); - describe('# Actions', function() { + describe('# Actions', function () { - it('should include `noun`', function() { assert(Sentencer.actions.noun); }); - it('should include `a_noun`', function() { assert(Sentencer.actions.a_noun); }); - it('should include `nouns`', function() { assert(Sentencer.actions.nouns); }); - it('should include `adjective`', function() { assert(Sentencer.actions.adjective); }); - it('should include `an_adjective`', function() { assert(Sentencer.actions.an_adjective); }); + it('should include `noun`', function () { assert(Sentencer.actions.noun); }); + it('should include `a_noun`', function () { assert(Sentencer.actions.a_noun); }); + it('should include `nouns`', function () { assert(Sentencer.actions.nouns); }); + it('should include `adjective`', function () { assert(Sentencer.actions.adjective); }); + it('should include `an_adjective`', function () { assert(Sentencer.actions.an_adjective); }); }); }); - describe('API', function() { + describe('API', function () { - it('should include a `configure` function', function() { + it('should include a `configure` function', function () { assert(Sentencer.configure); }); - it('should merge a new action', function() { + it('should merge a new action', function () { Sentencer.configure({ actions: { - firstNewAction: function() { return 'hello'; } + firstNewAction: function () { return 'hello'; } } }); assert.equal(Sentencer.actions.firstNewAction(), 'hello'); }); - it('should accept another action merge later', function() { + it('should accept another action merge later', function () { Sentencer.configure({ actions: { - secondNewAction: function() { return 'hello again'; } + secondNewAction: function () { return 'hello again'; } } }); @@ -61,67 +61,88 @@ describe('Sentencer:', function() { assert.equal(Sentencer.actions.secondNewAction(), 'hello again', 'second action exists as well'); }); - it('should include a `make` function', function() { + it('should include a `make` function', function () { assert(Sentencer.make); }); + it('should merge a new custom list', function () { + Sentencer.configure({ + customLists: [ + { + key: "animal", + values: ["dog", "cat", "elephant"], + articlize: "an_animal", // if named, add action that calls articlize + pluralize: "animals" // if named, add action that calls pluralize + } + ] + }); + + assert(Sentencer.actions.animal); + assert(Sentencer.actions.an_animal); + assert(Sentencer.actions.animals); + + assert.notEqual(["dog", "cat", "elephant"].indexOf(Sentencer.actions.animal()), -1, "missing animal"); + assert.notEqual(["a dog", "a cat", "an elephant"].indexOf(Sentencer.actions.an_animal()), -1, "missing an_animal"); + assert.notEqual(["dogs", "cats", "elephants"].indexOf(Sentencer.actions.animals()), -1, "missing animals"); + }); + }); - describe('Templating', function() { + describe('Templating', function () { - describe('# Default Actions', function() { + describe('# Default Actions', function () { - it('{{ noun }}', function(){ assert(Sentencer.make('{{ noun }}')); }); - it('{{ a_noun }}', function(){ assert(Sentencer.make('{{ a_noun }}')); }); - it('{{ nouns }}', function(){ assert(Sentencer.make('{{ nouns }}')); }); - it('{{ adjective }}', function(){ assert(Sentencer.make('{{ adjective }}')); }); - it('{{ an_adjective }}', function(){ assert(Sentencer.make('{{ an_adjective }}')); }); + it('{{ noun }}', function () { assert(Sentencer.make('{{ noun }}')); }); + it('{{ a_noun }}', function () { assert(Sentencer.make('{{ a_noun }}')); }); + it('{{ nouns }}', function () { assert(Sentencer.make('{{ nouns }}')); }); + it('{{ adjective }}', function () { assert(Sentencer.make('{{ adjective }}')); }); + it('{{ an_adjective }}', function () { assert(Sentencer.make('{{ an_adjective }}')); }); }); - describe('# Custom Actions', function() { + describe('# Custom Actions', function () { - it('{{ firstNewAction }}', function(){ + it('{{ firstNewAction }}', function () { assert.equal(Sentencer.make('{{ firstNewAction }}'), 'hello'); }); - it('{{ secondNewAction }}', function(){ + it('{{ secondNewAction }}', function () { assert.equal(Sentencer.make('{{ secondNewAction }}'), 'hello again'); }); - it('should return {{ action }} if it does not exist', function(){ - assert.equal( Sentencer.make('{{ nonexistant thing }}'), '{{ nonexistant thing }}'); + it('should return {{ action }} if it does not exist', function () { + assert.equal(Sentencer.make('{{ nonexistant thing }}'), '{{ nonexistant thing }}'); }); }); - describe('# Custom Actions With Arguments', function() { + describe('# Custom Actions With Arguments', function () { Sentencer.configure({ actions: { - withArgument: function(number) { + withArgument: function (number) { return number; }, - withArguments: function() { + withArguments: function () { return arguments.length; } } }); - it('should allow an action with one argument', function() { - assert.equal( Sentencer.make('{{ withArgument(1) }}'), 1 ); + it('should allow an action with one argument', function () { + assert.equal(Sentencer.make('{{ withArgument(1) }}'), 1); }); - it('should allow an action with multiple arguments', function() { - assert.equal( Sentencer.make('{{ withArguments(1,2,3) }}'), 3 ); + it('should allow an action with multiple arguments', function () { + assert.equal(Sentencer.make('{{ withArguments(1,2,3) }}'), 3); }); - it('should cast arguments as numbers when possible, otherwise strings', function() { + it('should cast arguments as numbers when possible, otherwise strings', function () { var result = null; Sentencer.configure({ actions: { - test: function() { + test: function () { result = Array.prototype.slice.call(arguments); } } @@ -131,18 +152,18 @@ describe('Sentencer:', function() { assert.deepEqual(result, [1, 'hey hello', 2]); }); - it('should fail silently if an action with arguments does not exist', function() { - assert.deepEqual( Sentencer.make('{{ nonExistantThing(1,2,3) }}'), '' ); + it('should fail silently if an action with arguments does not exist', function () { + assert.deepEqual(Sentencer.make('{{ nonExistantThing(1,2,3) }}'), ''); }); - it('pass text through if someone tries to exploit eval', function() { + it('pass text through if someone tries to exploit eval', function () { assert.deepEqual( Sentencer.make('{{ nothing; console.log("This should not evaluate"); }}'), '{{ nothing; console.log("This should not evaluate"); }}' ); }); - it('should pass text through when handed some garbage', function() { + it('should pass text through when handed some garbage', function () { assert.deepEqual( Sentencer.make('{{ &@#&(%*@$UU#I$HTRIGUHW$@) }}'), '{{ &@#&(%*@$UU#I$HTRIGUHW$@) }}' @@ -151,12 +172,27 @@ describe('Sentencer:', function() { }); + describe('# Custom Lists', function () { + + it('{{ animal }}', function () { + assert.notEqual(["dog", "cat", "elephant"].indexOf(Sentencer.make('{{ animal }}')), -1, "missing animal"); + }); + + it('{{ an_animal }}', function () { + assert.notEqual(["a dog", "a cat", "an elephant"].indexOf(Sentencer.make('{{ an_animal }}')), -1, "missing an_animal"); + }); + + it('{{ animals }}', function () { + assert.notEqual(["dogs", "cats", "elephants"].indexOf(Sentencer.make('{{ animals }}')), -1, "missing animals"); + }); + + }); }); - describe('Test Print', function() { + describe('Test Print', function () { - it('should have logged a sentence', function() { - console.log( Sentencer.make(" Here is {{ an_adjective }} sentence generated by Sentencer's {{ nouns }}.") ); + it('should have logged a sentence', function () { + console.log(Sentencer.make(" Here is {{ an_adjective }} sentence generated by Sentencer's {{ nouns }}.")); }); });