diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..d2a57ce --- /dev/null +++ b/.eslintrc @@ -0,0 +1,67 @@ +// adapted from: https://github.com/Khan/style-guides/pull/25 +{ + "plugins": [ + ], + "ecmaFeatures": { + "modules": true + }, + "env": { + "browser": true, + "node": true, + "mocha": true + }, + "rules": { + "arrow-parens": [2, "always"], + "arrow-spacing": 2, + "brace-style": 2, + "camelcase": [2, { + "properties": "always" + }], + "comma-dangle": [1, "always-multiline"], + "comma-dangle": [2, "never"], + "comma-spacing": [2, { + "before": false, + "after": true + }], + "eol-last": [0], + "guard-for-in": 2, + //"indent": [2, 4], + "linebreak-style": [2, "unix"], + /* + "max-len": [2, 80, 4, { + "ignoreUrls": true, + "ignorePattern": "^\\s*const\\s.+=\\s*require\\s*\\(" + }], + */ + "no-alert": 2, + "no-array-constructor": 2, + "no-const-assign": 2, + "no-debugger": 2, + "no-dupe-keys": 2, + "no-new-object": 2, + "no-spaced-func": 2, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-unreachable": 2, + "no-unused-vars": 1, + "no-var": 0, + //"object-curly-spacing": [2, "always"], + "one-var": [2, "never"], + "prefer-const": 1, + "prefer-template": 2, + "quotes": [2, "single"], + "semi": [2, "always"], + "space-after-keywords": [2, "always"], + "space-before-blocks": 2, + "space-before-function-paren": [2, { + "anonymous": "always", + "named": "never" + }], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "strict": [0, "never"], + "valid-jsdoc": 2 + }, + "extends": "eslint:recommended" +} diff --git a/.gitignore b/.gitignore index 2ccbe46..cb281a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -/node_modules/ +_ignore/ +coverage/ +node_modules/ +.DS_Store +.npm-debug.log +.project diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 8c86fc7..0000000 --- a/.jshintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "curly": true, - "eqeqeq": true, - "immed": true, - "latedef": true, - "newcap": true, - "noarg": true, - "sub": true, - "undef": true, - "unused": true, - "boss": true, - "eqnull": true, - "node": true -} diff --git a/.npmignore b/.npmignore index 0ec229d..02abe38 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,7 @@ .git* _ignore/ -test/ .DS_Store +.gitignore .npm-debug.log .project .travis.yml diff --git a/.tonic.example.js b/.tonic.example.js new file mode 100644 index 0000000..a8a9e2f --- /dev/null +++ b/.tonic.example.js @@ -0,0 +1,13 @@ +var Picker = require('random-picker').Picker; +var picker = new Picker(); // or pass in a seed: var picker = new Picker(seed) +picker.option('male'); +picker.option('female'); +var r1 = picker.pick(); // 50% chance of being 'male' or 'female' +picker.option('male', 22); +picker.option('female', 78); +var r2 = picker.pick(); // now there's a 22% chance of being 'male', and a 78% change of being 'female' +picker.removeAll(); +picker.option('good', 3.42); +picker.option('bad', 3.42); +var r3 = picker.pick(); // now there's a 50% chance of being 'good' or 'bad' +console.log(r1, r2, r3); diff --git a/.travis.yml b/.travis.yml index 816f195..e04c140 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,14 @@ language: node_js node_js: - - "0.10" -before_install: npm install -g grunt-cli -install: npm install + - 'stable' + - '4.2' + - '4.1' + - '4.0' + - '0.12' + - '0.11' + - '0.10' +before_script: + - npm install -g gulp +script: + - gulp test + - cat ./coverage/lcov.info | ./node_modules/.bin/coveralls --verbose diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index ff37751..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -module.exports = function(grunt) { - - // Project configuration. - grunt.initConfig({ - nodeunit: { - files: ['test/**/*_test.js'], - }, - jshint: { - options: { - jshintrc: '.jshintrc' - }, - gruntfile: { - src: 'Gruntfile.js' - }, - lib: { - src: ['lib/**/*.js'] - }, - test: { - src: ['test/**/*.js'] - }, - }, - watch: { - gruntfile: { - files: '<%= jshint.gruntfile.src %>', - tasks: ['jshint:gruntfile'] - }, - lib: { - files: '<%= jshint.lib.src %>', - tasks: ['jshint:lib', 'nodeunit'] - }, - test: { - files: '<%= jshint.test.src %>', - tasks: ['jshint:test', 'nodeunit'] - }, - }, - }); - - // These plugins provide necessary tasks. - grunt.loadNpmTasks('grunt-contrib-nodeunit'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-watch'); - - // Default task. - grunt.registerTask('default', ['jshint', 'nodeunit']); - -}; diff --git a/README.md b/README.md index 24640a4..d2d1ca4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # random-picker +[![NPM version](https://badge.fury.io/js/random-picker.svg)](http://badge.fury.io/js/random-picker) [![Build Status](https://travis-ci.org/skratchdot/random-picker.png?branch=master)](https://travis-ci.org/skratchdot/random-picker) -[![Coverage Status](https://coveralls.io/repos/skratchdot/random-picker/badge.png)](https://coveralls.io/r/skratchdot/random-picker) +[![Code Climate](https://codeclimate.com/github/skratchdot/random-picker.png)](https://codeclimate.com/github/skratchdot/random-picker) +[![Coverage Status](https://coveralls.io/repos/skratchdot/random-picker/badge.svg?branch=master&service=github)](https://coveralls.io/github/skratchdot/random-picker?branch=master) [![Dependency Status](https://david-dm.org/skratchdot/random-picker.svg)](https://david-dm.org/skratchdot/random-picker) [![devDependency Status](https://david-dm.org/skratchdot/random-picker/dev-status.svg)](https://david-dm.org/skratchdot/random-picker#info=devDependencies) +[![NPM](https://nodei.co/npm/random-picker.png)](https://npmjs.org/package/random-picker) + ## Description @@ -30,6 +34,8 @@ picker.option('bad', 3.42) picker.pick(); // now there's a 50% chance of being 'good' or 'bad' ``` +- [Live example on Tonic](https://tonicdev.com/npm/random-picker) + ## Documentation diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..9f5b4b2 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,62 @@ +/*eslint no-console: 0 */ +'use strict'; +var gulp = require('gulp'); +var eslint = require('gulp-eslint'); +var isparta = require('isparta'); +var istanbul = require('gulp-istanbul'); +var mocha = require('gulp-mocha'); +var files = { + lint: ['*.js'], + src: ['index.js'], + test: ['test.js'] +}; + +gulp.task('lint', function () { + return gulp.src(files.lint) + // eslint() attaches the lint output to the eslint property + // of the file object so it can be used by other modules. + .pipe(eslint()) + // eslint.format() outputs the lint results to the console. + // Alternatively use eslint.formatEach() (see Docs). + .pipe(eslint.format('stylish')) + // To have the process exit with an error code (1) on + // lint error, return the stream and pipe to failAfterError last. + .pipe(eslint.failAfterError()); +}); + +gulp.task('test', function () { + return gulp.src(files.src) + .pipe(istanbul({ + instrumenter: isparta.Instrumenter, + includeUntested: true + })) + // Force `require` to return covered files + .pipe(istanbul.hookRequire()) + .on('finish', function () { + gulp.src(files.test, {read: false}) + // gulp-mocha needs filepaths so you can't have any plugins before it + .pipe(mocha({ + reporter: 'spec' + })) + .on('error', function (err) { + console.error(err.toString()); + this.emit('end'); + }) + .pipe(istanbul.writeReports({ + dir: './coverage', + reporters: ['lcov', 'json', 'text-summary'] + })); + }); +}); + +gulp.task('watch', function () { + gulp.watch([files.lint], ['lint', 'test']); +}); + +// setup default task +gulp.task('default', ['lint', 'test', 'watch']); + +// handle errors +process.on('uncaughtException', function (e) { + console.error(e); +}); diff --git a/lib/random-picker.js b/index.js similarity index 86% rename from lib/random-picker.js rename to index.js index d68be8f..a55e797 100644 --- a/lib/random-picker.js +++ b/index.js @@ -10,7 +10,7 @@ var randomGenerator = require('random-seed'); var cleanScore = function (score) { - var num = score === 0 ? 0 : parseFloat(score) || 1; + var num = parseFloat(score) || 1; if (!Number.isFinite(num) || num < 0) { num = 0; } @@ -23,10 +23,10 @@ var Option = function (value, score) { }; var Picker = function (seed) { - var api = this, - rand = randomGenerator.create(seed), - total = 0, - options = []; + var api = this; + var rand = randomGenerator.create(seed); + var total = 0; + var options = []; var recalculateTotal = function () { total = 0; @@ -36,8 +36,8 @@ var Picker = function (seed) { }; api.pick = function () { - var runningTotal = 0, - randomValue = rand.floatBetween(0, total); + var runningTotal = 0; + var randomValue = rand.floatBetween(0, total); for (var i = 0; i < options.length; i++) { runningTotal += options[i].score; if (randomValue < runningTotal) { @@ -48,7 +48,8 @@ var Picker = function (seed) { }; api.option = function (value, score) { - var option = new Option(value, score), added = false; + var option = new Option(value, score); + var added = false; if (option.score <= 0) { api.remove(value); } else { diff --git a/package.json b/package.json index 59dd649..ca928ed 100644 --- a/package.json +++ b/package.json @@ -1,46 +1,45 @@ { - "name": "random-picker", - "description": "Randomly pick an item from a set (using the specified probabilities).", - "version": "0.1.0", - "homepage": "https://github.com/skratchdot/random-picker", - "author": "skratchdot", - "repository": { - "type": "git", - "url": "git://github.com/skratchdot/random-picker.git" - }, - "bugs": { - "url": "https://github.com/skratchdot/random-picker/issues" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/skratchdot/random-picker/blob/master/LICENSE-MIT" - } - ], - "main": "lib/random-picker", - "engines": { - "node": ">= 0.10.0" - }, - "scripts": { - "test": "grunt nodeunit" - }, - "dependencies": { - "random-seed": "^0.2.0" - }, - "devDependencies": { - "grunt": "~0.4.5", - "grunt-contrib-jshint": "~0.10.0", - "grunt-contrib-nodeunit": "~0.4.0", - "grunt-contrib-watch": "~0.6.1" - }, - "keywords": [ - "random", - "choice", - "selection", - "pick", - "picker", - "probability", - "hashset", - "hash" - ] -} + "name": "random-picker", + "version": "0.2.0", + "description": "Randomly pick an item from a set (using the specified probabilities).", + "main": "lib/random-picker", + "scripts": { + "test": "gulp test" + }, + "author": "skratchdot", + "license": "MIT", + "bugs": { + "url": "https://github.com/skratchdot/random-picker/issues" + }, + "homepage": "https://github.com/skratchdot/random-picker", + "repository": { + "type": "git", + "url": "git://github.com/skratchdot/random-picker.git" + }, + "engines": { + "node": ">= 0.10.0" + }, + "dependencies": { + "random-seed": "^0.3.0" + }, + "devDependencies": { + "chai": "^3.4.1", + "coveralls": "^2.11.4", + "gulp": "^3.9.0", + "gulp-eslint": "^1.1.1", + "gulp-istanbul": "^0.10.3", + "gulp-mocha": "^2.2.0", + "isparta": "^4.0.0" + }, + "tonicExampleFilename": ".tonic.example.js", + "keywords": [ + "random", + "choice", + "selection", + "pick", + "picker", + "probability", + "hashset", + "hash" + ] +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..59bbfeb --- /dev/null +++ b/test.js @@ -0,0 +1,82 @@ +'use strict'; + +var expect = require('chai').expect; +var Picker = require('./index').Picker; +var picker; +var pickerSeeded; +var seed = 'My random seed value!!!!'; +var iterations = 1000; +var i; + +describe('random-picker', function () { + beforeEach(function () { + picker = new Picker(); + pickerSeeded = new Picker(seed); + }); + it('simple test', function () { + picker.option('male'); + picker.option('female'); + for (i = 0; i < iterations; i++) { + expect(['male', 'female'].indexOf(picker.pick())).to.be.within(0, 1); + } + picker.remove('female'); + for (i = 0; i < iterations; i++) { + expect(picker.pick()).to.equal('male'); + } + }); + it('no options', function () { + expect(picker.pick()).to.be.null; + expect(pickerSeeded.pick()).to.be.null; + }); + it('seeded picker', function () { + pickerSeeded.option('male'); + pickerSeeded.option('female'); + expect(pickerSeeded.pick()).to.equal('female'); + expect(pickerSeeded.pick()).to.equal('female'); + expect(pickerSeeded.pick()).to.equal('male'); + expect(pickerSeeded.pick()).to.equal('male'); + expect(pickerSeeded.pick()).to.equal('female'); + expect(pickerSeeded.pick()).to.equal('male'); + }); + it('removeAll()', function () { + picker.option('male'); + picker.option('female'); + expect(['male', 'female'].indexOf(picker.pick())).to.be.within(0, 1); + expect(function () { + picker.removeAll(); + }).to.not.throw(Error); + expect(picker.pick()).to.be.null; + }); + it('size(), totalScore(), avgScore()', function () { + expect(picker.avgScore()).to.equal(0); + picker.option('male'); + picker.option('female'); + expect(picker.size()).to.equal(2); + expect(picker.totalScore()).to.equal(2); + expect(picker.avgScore()).to.equal(1); + picker.option('male', 4); + picker.option('female', 4); + picker.option('uknown', 7); + expect(picker.size()).to.equal(3); + expect(picker.totalScore()).to.equal(15); + expect(picker.avgScore()).to.equal(5); + }); + it('option(name, value) should work', function () { + picker.option('male', 10); + picker.option('female', 10); + for (i = 0; i < iterations; i++) { + expect(['male', 'female'].indexOf(picker.pick())).to.be.within(0, 1); + } + picker.option('male', -1); // removes option + for (i = 0; i < iterations; i++) { + expect(picker.pick()).to.equal('female'); + } + }); + it('should work with invalid option values', function () { + picker.option('male', 'a'); + picker.option('female', 'b'); + for (i = 0; i < iterations; i++) { + expect(['male', 'female'].indexOf(picker.pick())).to.be.within(0, 1); + } + }); +}); diff --git a/test/random-picker_test.js b/test/random-picker_test.js deleted file mode 100644 index ea923fe..0000000 --- a/test/random-picker_test.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * random-picker - * https://github.com/skratchdot/random-picker - * - * Copyright (c) 2014 skratchdot - * Licensed under the MIT license. - */ -/* - ======== A Handy Little Nodeunit Reference ======== - https://github.com/caolan/nodeunit - - Test methods: - test.expect(numAssertions) - test.done() - Test assertions: - test.ok(value, [message]) - test.equal(actual, expected, [message]) - test.notEqual(actual, expected, [message]) - test.deepEqual(actual, expected, [message]) - test.notDeepEqual(actual, expected, [message]) - test.strictEqual(actual, expected, [message]) - test.notStrictEqual(actual, expected, [message]) - test.throws(block, [error], [message]) - test.doesNotThrow(block, [error], [message]) - test.ifError(value) -*/ -'use strict'; - -var Picker = require('../lib/random-picker.js').Picker; -var picker, pickerSeeded; -var seed = 'My random seed value!!!!'; -var iterations = 1000; - -exports['random-picker tests'] = { -setUp: function (done) { - picker = new Picker(); - pickerSeeded = new Picker(seed); - done(); - }, - 'simple test': function (test) { - var i; - test.expect(iterations * 2); - picker.option('male'); - picker.option('female'); - for (i = 0; i < iterations; i++) { - test.ok(['male','female'].indexOf(picker.pick()) >= 0, 'should be male or female.'); - } - picker.remove('female'); - for (i = 0; i < iterations; i++) { - test.ok(picker.pick() === 'male', 'should be male.'); - } - test.done(); - }, - 'no options': function (test) { - test.expect(1); - test.equal(picker.pick(), null, 'should be null'); - test.done(); - }, - 'seeded picker': function (test) { - test.expect(6); - pickerSeeded.option('male'); - pickerSeeded.option('female'); - test.equal(pickerSeeded.pick(), 'female', 'should always be female.'); - test.equal(pickerSeeded.pick(), 'female', 'should always be female.'); - test.equal(pickerSeeded.pick(), 'male', 'should always be male.'); - test.equal(pickerSeeded.pick(), 'male', 'should always be male.'); - test.equal(pickerSeeded.pick(), 'female', 'should always be female.'); - test.equal(pickerSeeded.pick(), 'male', 'should always be male.'); - test.done(); - }, - 'removeAll()': function (test) { - test.expect(2); - picker.option('male'); - picker.option('female'); - test.ok(['male','female'].indexOf(picker.pick()) >= 0, 'should be male or female.'); - picker.removeAll(); - test.equal(picker.pick(), null, 'should be null.'); - test.done(); - }, - 'size(), totalScore(), avgScore()': function (test) { - test.expect(6); - picker.option('male'); - picker.option('female'); - test.equal(picker.size(), 2); - test.equal(picker.totalScore(), 2); - test.equal(picker.avgScore(), 1); - picker.option('male', 4); - picker.option('female', 4); - picker.option('uknown', 7); - test.equal(picker.size(), 3); - test.equal(picker.totalScore(), 15); - test.equal(picker.avgScore(), 5); - test.done(); - } -};