├── .gitignore ├── rambo.jpg ├── .npmrc ├── src ├── index.js ├── is-pair.js ├── is-named.js ├── lookup-ramda-function.js ├── grab-spec.js ├── grab.js ├── check-solution.js ├── produce.js ├── solve.js └── derive-functions.js ├── tests ├── same.js ├── flatten-spec.js ├── tail-spec.js ├── from-pairs-spec.js ├── filter-spec.js ├── split-spec.js ├── fn-string-spec.js ├── append-spec.js ├── range-spec.js ├── has-spec.js ├── add-spec.js ├── is-number-spec.js ├── gt-spec.js └── rambo-spec.js ├── .travis.yml ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /rambo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/rambo/HEAD/rambo.jpg -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | progress=false 4 | 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | solve: require('./solve'), 5 | produce: require('./produce') 6 | } 7 | -------------------------------------------------------------------------------- /src/is-pair.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function isPair (x) { 4 | return Array.isArray(x) && x.length === 2 5 | } 6 | 7 | module.exports = isPair 8 | -------------------------------------------------------------------------------- /src/is-named.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const is = require('check-more-types') 4 | const isNamed = is.schema({ 5 | f: is.fn, 6 | name: is.unemptyString 7 | }) 8 | 9 | module.exports = isNamed 10 | -------------------------------------------------------------------------------- /tests/same.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const la = require('lazy-ass') 5 | 6 | const same = function (a, b) { 7 | const info = Array.from(arguments).slice(2) 8 | la.apply(null, [R.equals(a, b)].concat(info)) 9 | } 10 | 11 | module.exports = same 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '4' 10 | - '6' 11 | before_script: 12 | - npm prune 13 | after_success: 14 | - npm run semantic-release 15 | branches: 16 | except: 17 | - "/^v\\d+\\.\\d+\\.\\d+$/" 18 | -------------------------------------------------------------------------------- /src/lookup-ramda-function.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | 7 | la(R, 'missing R library') 8 | 9 | // maybe we can just keep passing around expressions instead of forming functions? 10 | function lookupRamdaFunction (name) { 11 | la(is.unemptyString(name), 'missing ramda function', name) 12 | /*eslint-disable no-eval*/ 13 | const f = eval(name) 14 | la(is.fn(f), 'could not lookup name', name) 15 | return { 16 | f: f, 17 | name: name 18 | } 19 | } 20 | 21 | module.exports = lookupRamdaFunction 22 | -------------------------------------------------------------------------------- /tests/flatten-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const solve = require('..').solve 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | const isNamed = require('../src/is-named') 7 | 8 | /* global describe, it */ 9 | describe('R.flatten', () => { 10 | const input = [1, [2], [3, [4]]] 11 | const output = [1, 2, 3, 4] 12 | 13 | it('finds the solution', () => { 14 | const solution = solve(input, output) 15 | la(solution, 'returns solution', solution) 16 | la(isNamed(solution), solution) 17 | la(is.fn(solution.f), 'finds function') 18 | la(solution.name === 'R.flatten', solution) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/tail-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const solve = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | const isNamed = require('../src/is-named') 9 | 10 | describe('R.tail', () => { 11 | const input = [[1], [2], [3]] 12 | const output = [[2], [3]] 13 | const expected = `R.tail` 14 | 15 | it('finds the solution', () => { 16 | const solution = solve(input, output) 17 | la(solution, 'returns solution', solution) 18 | la(isNamed(solution), solution) 19 | la(is.fn(solution.f), 'finds function') 20 | la(solution.name === expected, solution.name) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/from-pairs-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const solve = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | const isNamed = require('../src/is-named') 9 | 10 | describe('R.fromPairs', () => { 11 | const input = [['a', 1], ['b', 2], ['c', 3]] 12 | const output = {a: 1, b: 2, c: 3} 13 | 14 | it('finds the solution', () => { 15 | const solution = solve(input, output) 16 | la(solution, 'returns solution', solution) 17 | la(isNamed(solution), solution) 18 | la(is.fn(solution.f), 'finds function') 19 | la(solution.name === 'R.fromPairs', solution) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/filter-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const solve = require('..').solve 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | const same = require('./same') 7 | 8 | /* global describe, it */ 9 | describe('R.filter', () => { 10 | describe('R.filter(R.is(Number))', () => { 11 | const solution = solve([1, 2, 'foo', 'bar', -10], [1, 2, -10]) 12 | 13 | it('finds solution from the examples', () => { 14 | la(is.fn(solution.f), 'found solution function') 15 | }) 16 | 17 | it('works', () => { 18 | const i = [4, 10, null, 'foo', 'f'] 19 | const expected = [4, 10] 20 | const computed = solution.f(i) 21 | same(computed, expected, 'computed', computed) 22 | }) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/split-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const solve = require('..').solve 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | const same = require('./same') 7 | const isNamed = require('../src/is-named') 8 | 9 | describe('R.split', () => { 10 | const input = '/usr/local/bin/node' 11 | const output = ['', 'usr', 'local', 'bin', 'node'] 12 | const expected = `R.split('/')` 13 | 14 | it('finds the solution', () => { 15 | const solution = solve(input, output) 16 | la(solution, 'returns solution', solution) 17 | la(isNamed(solution), solution) 18 | la(is.fn(solution.f), 'finds function') 19 | la(solution.name === expected, solution.name) 20 | }) 21 | }) 22 | 23 | // TODO add tail like the doc example 24 | // http://ramdajs.com/docs/#split 25 | -------------------------------------------------------------------------------- /tests/fn-string-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const la = require('lazy-ass') 4 | const is = require('check-more-types') 5 | 6 | /* global describe, it */ 7 | describe('solution text', () => { 8 | function add (a, b) { 9 | return a + b 10 | } 11 | 12 | it('uses default function toString', () => { 13 | const s = add.toString() 14 | la(is.unemptyString(s), s) 15 | }) 16 | 17 | it('has function add', () => { 18 | const s = add.toString() 19 | la(is.contains(s, 'function add'), s) 20 | }) 21 | 22 | it('can set toString', () => { 23 | const add10 = add.bind(null, 10) 24 | const description = 'add10(x)' 25 | add10.toString = () => description 26 | la(add10(2) === 12, 'works') 27 | const s = add10.toString() 28 | la(s === description, s) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /src/grab-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const grab = require('./grab') 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | const same = require('../tests/same') 7 | 8 | /* global describe, it */ 9 | describe('grab', () => { 10 | describe('number pairs', () => { 11 | it('is a function', () => { 12 | la(is.fn(grab.numberPairs)) 13 | }) 14 | 15 | it('finds just number pairs from single array', () => { 16 | const i = ['foo', [1, 2], 'bar', [3, 4]] 17 | const o = grab.numberPairs(i) 18 | const e = [[1, 2], [3, 4]] 19 | same(o, e, 'pairs', o) 20 | }) 21 | 22 | it('returns just numbers', () => { 23 | const i = [['foo', 3], [1, 2], 'bar', [3, 4]] 24 | const o = grab.numberPairs(i) 25 | const e = [[1, 2], [3, 4]] 26 | same(o, e, 'pairs', o) 27 | }) 28 | 29 | it('flattens single level', () => { 30 | const i = [ 31 | [['foo', 3], true], 32 | [[1, 2], true], 33 | ['bar', true], 34 | [[3, 4], false] 35 | ] 36 | const o = grab.numberPairs(i) 37 | const e = [[1, 2], [3, 4]] 38 | same(o, e, 'pairs', o) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /tests/append-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const solve = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | const isNamed = require('../src/is-named') 9 | 10 | describe('R.append', () => { 11 | describe('R.append(42)', () => { 12 | const input = ['foo', 'bar'] 13 | const output = ['foo', 'bar', 42] 14 | const expected = `R.append(42)` 15 | 16 | it('finds the solution', () => { 17 | const solution = solve(input, output) 18 | la(solution, 'returns solution', solution) 19 | la(isNamed(solution), solution) 20 | la(is.fn(solution.f), 'finds function') 21 | la(solution.name === expected, solution.name) 22 | }) 23 | }) 24 | 25 | describe(`R.append('last')`, () => { 26 | const input = ['foo', 'bar'] 27 | const output = ['foo', 'bar', 'last'] 28 | const expected = `R.append('last')` 29 | 30 | it('finds the solution', () => { 31 | const solution = solve(input, output) 32 | la(solution, 'returns solution', solution) 33 | la(isNamed(solution), solution) 34 | la(is.fn(solution.f), 'finds function') 35 | la(solution.name === expected, solution.name) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /src/grab.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const is = require('check-more-types') 5 | const isPair = require('./is-pair') 6 | const allNumbers = is.arrayOf.bind(null, is.number) 7 | 8 | function grabNumbers (things) { 9 | return R.uniq(R.flatten(things).filter(R.is(Number))) 10 | } 11 | 12 | function grabNumberPairs (things) { 13 | var pairs = [] 14 | if (!is.array(things)) { 15 | return pairs 16 | } 17 | 18 | things.forEach((x) => { 19 | if (isPair(x) && allNumbers(x)) { 20 | return pairs.push(x) 21 | } 22 | if (is.array(x)) { 23 | const xPairs = grabNumberPairs(x) 24 | pairs = pairs.concat(xPairs) 25 | } 26 | }) 27 | return pairs 28 | } 29 | 30 | function grabEverything (things) { 31 | return R.uniq(R.flatten(things)) 32 | } 33 | 34 | function grabProperties (things) { 35 | const objects = R.filter(R.is(Object), R.flatten(things)) 36 | const keys = R.uniq(R.flatten(R.map(R.keys, objects))) 37 | return keys 38 | } 39 | 40 | function grabStrings (things) { 41 | const strings = R.flatten(R.filter(R.is(String), R.flatten(things))) 42 | return strings 43 | } 44 | 45 | module.exports = { 46 | numbers: grabNumbers, 47 | everything: grabEverything, 48 | properties: grabProperties, 49 | strings: grabStrings, 50 | numberPairs: grabNumberPairs 51 | } 52 | -------------------------------------------------------------------------------- /src/check-solution.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | 7 | function test (input, output, fn) { 8 | try { 9 | const o = fn(input) 10 | // console.log('comparing', o, 'with expected', output) 11 | return R.equals(o, output) 12 | } catch (err) { 13 | return false 14 | } 15 | } 16 | 17 | function testProduces (output, fn) { 18 | try { 19 | const o = fn() 20 | return R.equals(o, output) 21 | } catch (err) { 22 | return false 23 | } 24 | } 25 | 26 | function testApply (input, output, fn) { 27 | if (is.not.array(input)) { 28 | return false 29 | } 30 | 31 | try { 32 | const o = fn.apply(null, input) 33 | // console.log('comparing', o, 'with expected', output) 34 | return R.equals(o, output) 35 | } catch (err) { 36 | return false 37 | } 38 | } 39 | 40 | function testExamples (tester, examples, fn) { 41 | la(is.fn(tester), 'missing tester function') 42 | if (is.not.fn(fn)) { 43 | return false 44 | } 45 | 46 | la(is.array(examples), 'invalid examples', examples) 47 | return examples.every((example) => { 48 | la(is.array(example) && example.length === 2, 'invalid example', example) 49 | // console.log('testing example', example[0], example[1]) 50 | return tester(example[0], example[1], fn) 51 | }) 52 | } 53 | 54 | module.exports = { 55 | test: test, 56 | testApply: testApply, 57 | testExamples: testExamples, 58 | testProduces: testProduces 59 | } 60 | -------------------------------------------------------------------------------- /tests/range-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const produce = require('..').produce 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | 9 | describe('R.range', () => { 10 | describe('R.range(1, 4)', () => { 11 | it('generates range', () => { 12 | const o = R.range(1, 4) 13 | same(o, [1, 2, 3], o) 14 | }) 15 | }) 16 | 17 | describe('finding range [50, 51, 52]', () => { 18 | const output = [50, 51, 52] 19 | const solution = produce(output) 20 | 21 | it('has function', () => { 22 | la(is.fn(solution.f), solution) 23 | }) 24 | 25 | it('produces the range', () => { 26 | const o = solution.f() 27 | same(o, output, 'produced', o) 28 | }) 29 | }) 30 | 31 | describe('finding range (1, 4)', () => { 32 | const output = [1, 2, 3] 33 | const solution = produce(output) 34 | 35 | it('returns a solution', () => { 36 | la(solution) 37 | }) 38 | 39 | it('has function', () => { 40 | la(is.fn(solution.f), solution) 41 | }) 42 | 43 | it('has a name', () => { 44 | la(is.unemptyString(solution.name), solution) 45 | }) 46 | }) 47 | 48 | describe('finding range (5, 9)', () => { 49 | const output = [5, 6, 7, 8] 50 | const solution = produce(output) 51 | 52 | it('returns a solution', () => { 53 | la(solution) 54 | }) 55 | 56 | it('has function', () => { 57 | la(is.fn(solution.f), solution) 58 | }) 59 | 60 | it('has a name', () => { 61 | la(is.unemptyString(solution.name), solution) 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /src/produce.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const la = require('lazy-ass') 4 | const is = require('check-more-types') 5 | const lookupR = require('./lookup-ramda-function') 6 | const isNamed = require('./is-named') 7 | const testProduces = require('./check-solution').testProduces 8 | const R = require('ramda') 9 | 10 | const simpleFunctions = [ 11 | 'R.range' 12 | ].map(lookupR) 13 | const fns = simpleFunctions 14 | 15 | function deriveRange (output) { 16 | if (is.not.array(output)) { 17 | return 18 | } 19 | if (is.empty(output)) { 20 | return 21 | } 22 | if (!output.every(is.number)) { 23 | return 24 | } 25 | const first = output[0] 26 | const last = R.last(output) 27 | la(is.number(last), 'invalid last element in list', output) 28 | const after = last + 1 29 | return { 30 | f: R.always(R.range(first, after)), 31 | name: `R.range(${first}, ${after})` 32 | } 33 | } 34 | 35 | function deriveProducer (output, f) { 36 | la(isNamed(f), 'not a named function', f) 37 | 38 | if (f.f === R.range) { 39 | const d = deriveRange(output) 40 | if (isNamed(d)) { 41 | return [d] 42 | } 43 | } 44 | 45 | return [] 46 | } 47 | 48 | function produce (output) { 49 | la(arguments.length === 1, 'expected single output argument', output) 50 | 51 | const derived = R.flatten(fns.map((f) => deriveProducer(output, f))) 52 | const all = fns.concat(derived) 53 | 54 | var found 55 | // try to apply just a function 56 | all.some((fn) => { 57 | la(isNamed(fn), fn) 58 | if (testProduces(output, fn.f)) { 59 | found = fn 60 | return true 61 | } 62 | }) 63 | if (found) { 64 | return found 65 | } 66 | } 67 | 68 | module.exports = produce 69 | -------------------------------------------------------------------------------- /tests/has-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const solve = require('..').solve 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | const isNamed = require('../src/is-named') 7 | 8 | /* global describe, it */ 9 | describe('R.has', () => { 10 | const examples = [ 11 | [{name: 'alice'}, true], 12 | [{name: 'bob'}, true], 13 | [{age: 42}, false], 14 | [{}, false] 15 | ] 16 | // should be R.has('name') 17 | 18 | it('finds the solution', () => { 19 | const solution = solve(examples) 20 | la(solution, 'returns solution', solution) 21 | la(isNamed(solution), solution) 22 | la(is.fn(solution.f), 'finds function') 23 | la(solution.name === 'R.has(\'name\')', solution) 24 | }) 25 | 26 | it('solution works', () => { 27 | const solution = solve(examples) 28 | const o = solution.f({name: 'john'}) 29 | la(o, 'expected to have name') 30 | }) 31 | }) 32 | 33 | describe('R.filter(R.has(name))', () => { 34 | const input = [{foo: 'bar'}, {name: 'alice'}, {name: 'bob'}, {foo: 42}] 35 | const output = [{name: 'alice'}, {name: 'bob'}] 36 | const expected = `R.filter(R.has('name'))` 37 | 38 | it('finds the solution', () => { 39 | const solution = solve(input, output) 40 | la(solution, 'returns solution', solution) 41 | la(isNamed(solution), solution) 42 | la(is.fn(solution.f), 'finds function') 43 | la(solution.name === expected, solution.name) 44 | }) 45 | }) 46 | 47 | describe('R.filter(R.has(foo))', () => { 48 | const input = [{foo: 'bar'}, {name: 'alice'}, {name: 'bob'}, {foo: 42}] 49 | const output = [{foo: 'bar'}, {foo: 42}] 50 | const expected = `R.filter(R.has('foo'))` 51 | 52 | it('finds the solution', () => { 53 | const solution = solve(input, output) 54 | la(solution, 'returns solution', solution) 55 | la(isNamed(solution), solution) 56 | la(is.fn(solution.f), 'finds function') 57 | la(solution.name === expected, solution.name) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /tests/add-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const rabot = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | const isNamed = require('../src/is-named') 9 | 10 | /* global describe, it */ 11 | describe('R.add(1)', () => { 12 | describe('adds multiple inputs', () => { 13 | const examples = [[1, 2], [5, 6]] 14 | const solution = rabot(examples) 15 | 16 | it('finds solution from the examples', () => { 17 | la(isNamed(solution)) 18 | la(is.fn(solution.f)) 19 | }) 20 | 21 | it('has description', () => { 22 | la(is.unemptyString(solution.name), solution) 23 | }) 24 | 25 | it('has right description', () => { 26 | const expected = 'R.add(1)' 27 | const s = solution.name 28 | la(s === expected, 'wrong human description', s) 29 | }) 30 | 31 | it('works on example', () => { 32 | la(solution.f(1) === 2, solution.f(1)) 33 | }) 34 | 35 | it('control', () => { 36 | const i = 10 37 | const o = 11 38 | const computed = solution.f(i) 39 | same(computed, o, computed) 40 | }) 41 | }) 42 | 43 | describe('R.map(R.add(4))', () => { 44 | const input = [1, 2, 3, 4] 45 | const output = [5, 6, 7, 8] 46 | 47 | it('is curried', () => { 48 | const fn = R.map(R.add(4)) 49 | la(is.fn(fn)) 50 | const o = fn(input) 51 | same(o, output, 'invalid map', o) 52 | }) 53 | 54 | it('finds map with right solution', () => { 55 | const solution = rabot(input, output) 56 | la(solution, 'returns solution', solution) 57 | la(isNamed(solution), solution) 58 | la(is.fn(solution.f), 'finds function') 59 | }) 60 | 61 | it('finds correct solution name', () => { 62 | const expected = 'R.map(R.add(4))' 63 | const solution = rabot(input, output) 64 | la(solution.name === expected, solution) 65 | }) 66 | 67 | it('can evaluate the solution name', () => { 68 | const solution = rabot(input, output) 69 | la(is.unemptyString(solution.name), solution) 70 | const f = eval(solution.name) 71 | la(is.fn(f), 'not a function from', solution.name) 72 | const o = f(input) 73 | la(is.array(o), 'output not an array', o) 74 | same(o, output, 'wrong output', o) 75 | }) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rambo", 3 | "description": "Automatic Ramda solution bot", 4 | "version": "0.0.0-semantic-release", 5 | "author": "Gleb Bahmutov ", 6 | "bugs": "https://github.com/bahmutov/rambo/issues", 7 | "config": { 8 | "pre-git": { 9 | "commit-msg": "simple", 10 | "pre-commit": [ 11 | "npm run deps", 12 | "npm test", 13 | "npm run ban" 14 | ], 15 | "pre-push": [ 16 | "npm run secure", 17 | "npm run license", 18 | "npm run ban -- --all", 19 | "npm run size" 20 | ], 21 | "post-commit": [], 22 | "post-merge": [] 23 | } 24 | }, 25 | "files": [ 26 | "src/*.js", 27 | "!src/*-spec.js" 28 | ], 29 | "engines": { 30 | "nodejs": ">=6" 31 | }, 32 | "homepage": "https://github.com/bahmutov/rambo#readme", 33 | "keywords": [ 34 | "bot", 35 | "fp", 36 | "functional", 37 | "ramda", 38 | "solution", 39 | "solver" 40 | ], 41 | "license": "MIT", 42 | "main": "src/index.js", 43 | "repository": { 44 | "type": "git", 45 | "url": "https://github.com/bahmutov/rambo.git" 46 | }, 47 | "scripts": { 48 | "ban": "ban", 49 | "deps": "deps-ok", 50 | "format": "standard-format -w src/*.js", 51 | "issues": "git-issues", 52 | "license": "license-checker --production --onlyunknown --csv", 53 | "lint": "standard --verbose src/*.js", 54 | "pretest": "npm run format && npm run lint", 55 | "secure": "nsp check", 56 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";", 57 | "test": "npm run unit", 58 | "unit": "mocha src/*-spec.js tests/*-spec.js", 59 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 60 | }, 61 | "devDependencies": { 62 | "@semantic-release/condition-travis": "4.1.4", 63 | "ban-sensitive-files": "1.8.2", 64 | "condition-node-version": "1.3.0", 65 | "deps-ok": "1.1.0", 66 | "git-issues": "1.2.0", 67 | "license-checker": "5.1.2", 68 | "mocha": "2.4.5", 69 | "nsp": "2.4.0", 70 | "pre-git": "3.8.3", 71 | "semantic-release": "^4.3.5", 72 | "standard": "7.0.1", 73 | "standard-format": "2.1.1" 74 | }, 75 | "dependencies": { 76 | "check-more-types": "2.20.2", 77 | "lazy-ass": "1.4.0", 78 | "ramda": "0.21.0" 79 | }, 80 | "release": { 81 | "verifyConditions": [ 82 | { 83 | "path": "@semantic-release/condition-travis" 84 | }, { 85 | "path": "condition-node-version", 86 | "node": "6" 87 | } 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/solve.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const la = require('lazy-ass') 4 | const is = require('check-more-types') 5 | const check = require('./check-solution') 6 | const isNamed = require('./is-named') 7 | const lookupR = require('./lookup-ramda-function') 8 | const derivedFunctions = require('./derive-functions') 9 | 10 | const simpleFunctions = [ 11 | 'R.is', 12 | 'R.add', 'R.subtract', 13 | 'R.gt', 14 | 'R.flatten', 'R.fromPairs', 'R.has', 15 | 'R.split', 'R.tail', 'R.append' 16 | ].map(lookupR) 17 | const iterators = [ 18 | 'R.map', 'R.filter' 19 | ] 20 | const iteratorFunctions = iterators.map(lookupR) 21 | 22 | const fns = simpleFunctions.concat(iteratorFunctions) 23 | 24 | function solve (examples) { 25 | if (arguments.length === 2) { 26 | // passed single example as separate input / output 27 | examples = [[arguments[0], arguments[1]]] 28 | // console.log(examples) 29 | } 30 | 31 | var allSimpleDerivedFunctions = [] 32 | 33 | allSimpleDerivedFunctions = simpleFunctions.reduce((prev, f) => { 34 | return prev.concat(derivedFunctions(examples, f, allSimpleDerivedFunctions)) 35 | }, []) 36 | 37 | var found 38 | // try to apply just a function 39 | fns.some((fn) => { 40 | la(isNamed(fn), fn) 41 | if (check.testExamples(check.test, examples, fn.f)) { 42 | found = fn 43 | return true 44 | } 45 | }) 46 | if (found) { 47 | return found 48 | } 49 | 50 | // try derived functions 51 | fns.some((fn) => { 52 | const derived = derivedFunctions(examples, fn, allSimpleDerivedFunctions) 53 | la(is.array(derived), 54 | 'could not get list of derived functions from', fn, 'got', derived) 55 | 56 | // console.log('testing functions') 57 | // console.log(R.map(R.prop('name'))(derived)) 58 | 59 | return derived.some((fder) => { 60 | la(isNamed(fder), 'invalid derived function', fder, 'from', fn) 61 | // console.log(fder.name, examples[0][0], fder.f(examples[0][1])) 62 | // console.log('trying', fder.name) 63 | if (check.testExamples(check.test, examples, fder.f)) { 64 | found = fder 65 | return true 66 | } 67 | }) 68 | }) 69 | if (found) { 70 | return found 71 | } 72 | 73 | // maybe applying inputs as separate arguments works 74 | fns.some((fn) => { 75 | la(isNamed(fn), 'invalid named for spread', fn) 76 | // console.log('testing spread over', fn.name) 77 | if (check.testExamples(check.testApply, examples, fn)) { 78 | // found = R.spread(fn) 79 | found = fn 80 | // console.log('fn', fn.name) 81 | return true 82 | } 83 | }) 84 | if (found) { 85 | return found 86 | } 87 | } 88 | 89 | module.exports = solve 90 | -------------------------------------------------------------------------------- /tests/is-number-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const solve = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | 9 | /* global describe, it */ 10 | describe('R.is(Number)', () => { 11 | describe('single level, multiple inputs', () => { 12 | const examples = [ 13 | [1, true], 14 | [5, true], 15 | ['foo', false] 16 | ] 17 | const solution = solve(examples) 18 | 19 | it('finds solution from the examples', () => { 20 | la(is.fn(solution.f), 'found solution function') 21 | }) 22 | 23 | it('has correct name', () => { 24 | la(solution.name, solution) 25 | la(solution.name === 'R.is(Number)', solution) 26 | }) 27 | 28 | it('works on other numbers', () => { 29 | la(solution.f(42) === true, 42, solution.f(42)) 30 | la(solution.f(-1) === true, -1, solution.f(-1)) 31 | la(solution.f(11) === true, 11, solution.f(11)) 32 | }) 33 | 34 | it('works on null', () => { 35 | la(solution.f(null) === false, solution.f(null)) 36 | }) 37 | }) 38 | 39 | describe('single level, multiple inputs for String', () => { 40 | const examples = [[1, false], [5, false], ['foo', true]] 41 | const solution = solve(examples) 42 | 43 | it('finds solution from the examples', () => { 44 | la(is.fn(solution.f), 'found solution function') 45 | }) 46 | 47 | it('works on other numbers', () => { 48 | la(solution.f(42) === false, solution.f(42)) 49 | la(solution.f(-1) === false, solution.f(-1)) 50 | la(solution.f('11') === true, solution.f('11')) 51 | }) 52 | }) 53 | 54 | describe('R.map(R.is(Number))', () => { 55 | const input = [1, 2, 'foo', 4] 56 | const output = [true, true, false, true] 57 | 58 | it('is curried', () => { 59 | const fn = R.map(R.is(Number)) 60 | la(is.fn(fn), 'is a function') 61 | const o = fn(input) 62 | same(o, output, 'invalid map', o) 63 | }) 64 | 65 | it('finds map with right solution', () => { 66 | const solution = solve(input, output) 67 | la(is.fn(solution.f)) 68 | }) 69 | 70 | it('works', () => { 71 | const i = [4, 10, null] 72 | const expected = [true, true, false] 73 | const solution = solve(input, output) 74 | const computed = solution.f(i) 75 | same(computed, expected, 'computed', computed) 76 | }) 77 | }) 78 | 79 | describe('R.map(R.is(String))', () => { 80 | const input = [1, 2, 'foo', 4] 81 | const output = [false, false, true, false] 82 | 83 | it('finds map with right solution', () => { 84 | const solution = solve(input, output) 85 | la(is.fn(solution.f)) 86 | }) 87 | 88 | it('works', () => { 89 | const i = [4, 10, null, 'foo', 'f'] 90 | const expected = [false, false, false, true, true] 91 | const solution = solve(input, output) 92 | const computed = solution.f(i) 93 | same(computed, expected, 'computed', computed) 94 | }) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /tests/gt-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const solve = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | 9 | /* global describe, it */ 10 | describe('R.gt', () => { 11 | it('passes if first number is larger than second', () => { 12 | la(R.gt(10, 9)) 13 | la(!R.gt(10, 11)) 14 | }) 15 | 16 | it('is curried', () => { 17 | const gt10 = R.gt(10) 18 | la(is.fn(gt10)) 19 | la(gt10(3)) 20 | }) 21 | 22 | describe('single input', () => { 23 | const examples = [ 24 | [[3, 1], true], 25 | [[3, 10], false] 26 | ] 27 | const solution = solve(examples) 28 | 29 | it('array concat with empty first array', () => { 30 | const first = [] 31 | const second = [42, 43] 32 | const result = first.concat(second) 33 | same(result, second) 34 | }) 35 | 36 | examples.forEach((example, k) => { 37 | it(`has valid example ${k}`, () => { 38 | const i = example[0] 39 | const expected = example[1] 40 | la(R.gt(i[0])(i[1]) === expected) 41 | }) 42 | 43 | it(`works with both arguments ${k}`, () => { 44 | const i = example[0] 45 | const expected = example[1] 46 | la(R.gt(i[0], i[1]) === expected) 47 | }) 48 | }) 49 | 50 | it('finds solution from the examples', () => { 51 | la(solution, 'returned a solution') 52 | la(is.fn(solution.f), 'has solution function') 53 | }) 54 | 55 | it('has spread flag', () => { 56 | la(solution.spread, solution) 57 | }) 58 | 59 | it('works on example', () => { 60 | // TODO even if examples are a spread, we probably want to 61 | // get back the original function 62 | const o = solution.f([3, 1]) 63 | same(o, true, o) 64 | }) 65 | 66 | // control examples 67 | const controls = [ 68 | [[10, 6], true], 69 | [[6, 10], false], 70 | [[6, 6], false] 71 | ] 72 | 73 | controls.forEach((control, k) => { 74 | it(`passes control ${k}`, () => { 75 | const i = control[0] 76 | const expected = control[1] 77 | la(R.gt(i[0])(i[1]) === expected, 'R.gt()', control) 78 | la(solution.f(i) === expected, 'solution', control) 79 | }) 80 | }) 81 | }) 82 | 83 | describe('R.filter(R.gt(10))', () => { 84 | const input = [10, 2, 3, 40] 85 | const output = [2, 3] 86 | const solution = solve(input, output) 87 | 88 | it('has solution using filter and gt', () => { 89 | const gt10 = R.gt(10) 90 | const o = R.filter(gt10, input) 91 | same(o, output, 'wrong output', o) 92 | }) 93 | 94 | it('finds solution from the examples', () => { 95 | la(is.fn(solution.f), 'found solution function') 96 | }) 97 | 98 | it.skip('works', () => { 99 | const i = [11, 4, 10] 100 | const expected = [4] 101 | const computed = solution.f(i) 102 | same(computed, expected, 'computed', computed) 103 | }) 104 | }) 105 | }) 106 | -------------------------------------------------------------------------------- /src/derive-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const la = require('lazy-ass') 5 | const is = require('check-more-types') 6 | const isNamed = require('./is-named') 7 | const grab = require('./grab') 8 | const isPair = require('./is-pair') 9 | 10 | const halfN = 10 11 | const firstValues = R.range(0, halfN * 2).map((x) => x - halfN) 12 | 13 | function deriveFunctions (examples, f, allSimpleDerivedFunctions) { 14 | la(is.array(examples), 'missing examples', examples) 15 | la(isNamed(f), 'not a named function', f) 16 | la(is.fn(f.f), 'expected function to derive', f) 17 | 18 | // derived functions specific 19 | // TODO use Symbol 20 | if (f.f === R.is) { 21 | return [{ 22 | f: R.is(Number), 23 | name: 'R.is(Number)' 24 | }, { 25 | f: R.is(Object), 26 | name: 'R.is(Object)' 27 | }, { 28 | f: R.is(String), 29 | name: 'R.is(String)' 30 | }] 31 | } 32 | if (f.f === R.map || f.f === R.filter) { 33 | // TODO replace with compose 34 | return allSimpleDerivedFunctions.map((df) => { 35 | la(isNamed(df), 'not a named function', df) 36 | return { 37 | f: f.f(df.f), 38 | name: `${f.name}(${df.name})` 39 | } 40 | }) 41 | } 42 | 43 | if (f.f === R.add || f.f === R.subtract || f.f === R.gt) { 44 | // binary functions 45 | const outsideData = firstValues.map((x) => { 46 | return { 47 | f: f.f(x), 48 | name: `${f.name}(${x})` 49 | } 50 | }) 51 | 52 | const asSpread = [{ 53 | f: R.apply(f.f), 54 | name: `R.apply(${f.name})`, 55 | spread: true 56 | }] 57 | 58 | const pairs = grab.numberPairs(examples) 59 | const guessesFromData = pairs.map((x) => { 60 | la(isPair(x), 'invalid number pair', x) 61 | // since we have both arguments for binary function, it evaluates 62 | // right away, thus create dummy function around it 63 | return { 64 | f: R.always(f.f(x[0], x[1])), 65 | name: `${f.name}(${x[0]}, ${x[1]})` 66 | } 67 | }) 68 | return asSpread.concat(guessesFromData).concat(outsideData) 69 | // return guessesFromData 70 | } 71 | 72 | if (f.f === R.has) { 73 | const propertyNames = grab.properties(examples) 74 | la(is.array(propertyNames), 'could not grab property names from', examples) 75 | return propertyNames.map((property) => { 76 | return { 77 | f: f.f(property), 78 | name: `${f.name}('${property}')` 79 | } 80 | }) 81 | } 82 | 83 | if (f.f === R.append) { 84 | const allValues = grab.everything(examples) 85 | la(is.array(allValues), 'could not grab values from', examples) 86 | return allValues.map((value) => { 87 | const s = is.string(value) ? `'${value}'` : value 88 | return { 89 | f: f.f(value), 90 | name: `${f.name}(${s})` 91 | } 92 | }) 93 | } 94 | 95 | if (f.f === R.split) { 96 | const strings = grab.strings(examples) 97 | 98 | la(is.array(strings), 'expected list of strings from', examples, strings) 99 | const characters = R.uniq(strings.join('').split('')) 100 | // console.log('characters', characters) 101 | return characters.map((sep) => { 102 | return { 103 | f: f.f(sep), 104 | name: `${f.name}('${sep}')` 105 | } 106 | }) 107 | } 108 | 109 | return [] 110 | } 111 | 112 | module.exports = deriveFunctions 113 | -------------------------------------------------------------------------------- /tests/rambo-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const R = require('ramda') 4 | const lobot = require('..').solve 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const same = require('./same') 8 | 9 | /* global describe, it */ 10 | describe.skip('lobot', () => { 11 | it('is a function', () => { 12 | la(is.fn(lobot)) 13 | }) 14 | 15 | it('_.subtract is curried', () => { 16 | const sub5 = R.subtract(5) 17 | la(is.fn(sub5)) 18 | const result = sub5(6) 19 | la(result === -1, result) 20 | }) 21 | 22 | it('_.add is curried', () => { 23 | const add5 = R.add(5) 24 | la(is.fn(add5)) 25 | la(add5(6) === 11) 26 | }) 27 | }) 28 | 29 | describe.skip('add multiple inputs', () => { 30 | const examples = [[1, 2], [5, 6]] 31 | const solution = lobot(examples) 32 | 33 | it('finds solution from the examples', () => { 34 | la(is.fn(solution)) 35 | }) 36 | 37 | it('works on example', () => { 38 | la(solution(1) === 2, solution(1)) 39 | }) 40 | 41 | it('control', () => { 42 | const i = 10 43 | const o = 11 44 | const computed = solution(i) 45 | la(R.equals(computed, o), computed) 46 | }) 47 | }) 48 | 49 | // TODO implement method-specific search 50 | // for example for _.get inspect the input object 51 | // and try all object property permutations 52 | describe.skip('get', () => { 53 | const input = {foo: {bar: 42}} 54 | const output = 42 55 | const solution = lobot(input, output) 56 | 57 | it('finds solution from the examples', () => { 58 | la(is.fn(solution)) 59 | }) 60 | 61 | it('works on example', () => { 62 | same(solution(input), output, solution(input)) 63 | }) 64 | 65 | it('works on control', () => { 66 | const o = {foo: {bar: 50}} 67 | same(solution(o), 50, 'got correct deep value', solution(o)) 68 | }) 69 | 70 | it.skip('works on control', () => { 71 | same(solution([10, 6]), true, solution([10, 6])) 72 | same(solution([6, 10]), false, solution([6, 10])) 73 | same(solution([6, 6]), false, solution([6, 6])) 74 | }) 75 | }) 76 | 77 | describe.skip('uniq', () => { 78 | const input = [2, 1, 2] 79 | const output = [2, 1] 80 | const solution = lobot(input, output) 81 | 82 | it('works', () => { 83 | const o = R.uniq(input) 84 | // console.log('o', o) 85 | la(R.equals(o, output)) 86 | }) 87 | 88 | it('finds solution from the examples', () => { 89 | la(is.fn(solution), 'found function solution') 90 | }) 91 | 92 | it('works on example', () => { 93 | const o = solution(input) 94 | la(R.equals(o, output), o) 95 | }) 96 | }) 97 | 98 | describe.skip('zipObject', () => { 99 | const input = [['a', 'b'], [1, 2]] 100 | const output = { 'a': 1, 'b': 2 } 101 | const solution = lobot(input, output) 102 | 103 | it('works', () => { 104 | const o = R.zipObject.apply(null, input) 105 | la(R.equals(o, output)) 106 | }) 107 | 108 | it('finds solution from the examples', () => { 109 | la(is.fn(solution), 'found function solution', solution) 110 | }) 111 | 112 | it('works on example', () => { 113 | const o = solution(input) 114 | same(o, output, o) 115 | }) 116 | 117 | it('control', () => { 118 | const i = [['foo'], [42]] 119 | const o = {foo: 42} 120 | const computed = solution(i) 121 | same(computed, o, computed) 122 | }) 123 | }) 124 | 125 | describe.skip('add', () => { 126 | const i = 1 127 | const o = 2 128 | const solution = lobot(i, o) 129 | 130 | it('examples', () => { 131 | la(is.fn(solution)) 132 | same(solution(i), o) 133 | }) 134 | 135 | it('control', () => { 136 | const i = 10 137 | const o = 11 138 | const computed = solution(i) 139 | same(computed, o, computed) 140 | }) 141 | }) 142 | 143 | describe.skip('castArray', () => { 144 | const input = 1 145 | const output = [1] 146 | const solution = lobot(input, output) 147 | 148 | it('finds solution from the examples', () => { 149 | la(is.fn(solution)) 150 | }) 151 | 152 | it('works on example', () => { 153 | same(solution(input), output) 154 | }) 155 | 156 | it('control', () => { 157 | same(solution(null), [null], solution(null)) 158 | same(solution('abc'), ['abc'], solution('abc')) 159 | }) 160 | }) 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rambo 2 | 3 | > Automatic Ramda solution bot 4 | 5 | ![rambo](rambo.jpg) 6 | 7 | Given inputs and outputs brute forces a Ramda solution. 8 | 9 | ## Example 10 | 11 | What Ramda solution given `[1, 2, 3, 4]` returns `[5, 6, 7, 8]`? 12 | 13 | ```js 14 | const solve = require('rambo').solve 15 | const solution = solve([1, 2, 3, 4], [5, 6, 7, 8]) 16 | console.log(solution.name) // "R.map(R.add(4))" 17 | // has "f" property with solution function 18 | console.log(solution.f([1, 2, 3, 4])) // [5, 6, 7, 8] 19 | // can apply to other inputs 20 | console.log(solution.f([20, 30])) // [24, 34] 21 | ``` 22 | 23 | ## Produce example 24 | 25 | Some cases do not have any inputs. For example, what Ramda command produces `[50, 51, 52]`? 26 | 27 | ```js 28 | const produce = require('rambo').produce 29 | const solution = produce([50, 51, 52]) 30 | console.log(solution.name) // "R.range(50, 53)" 31 | console.log(solution.f()) // [50, 51, 52] 32 | ``` 33 | 34 | ## Why? 35 | 36 | Because there are 200 functions in Ramda library, and I constantly have to look up 37 | [which function I should use](https://github.com/ramda/ramda/wiki/What-Function-Should-I-Use%3F). 38 | Plus there is `ram-bot` in the [Ramda Gitter channel](https://gitter.im/ramda/ramda) that given 39 | input and Ramda code computes the output. I wanted to find Ramda code that computes the answer! 40 | 41 | ``` 42 | // ram-bot 43 | Ramda code (inputs) ===> ? 44 | // Rambo 45 | Ramda ? (inputs) ===> outputs 46 | ``` 47 | 48 | ## How? 49 | 50 | The solver is **very simple** and just brute forces the solution by iterating over a bunch of 51 | functions. Since Ramda is sooooo good at currying, we can combine multiple functions by 52 | providing *derived* functions as inputs to other functions, like `R.map` for example, as first 53 | arguments. I also use the *data* to guide the solution tries. For example, `R.has(...)` tries 54 | every string from the input and output as a property name. 55 | 56 | ```js 57 | const solve = require('rambo').solve 58 | const input = [{foo: 'bar'}, {name: 'alice'}, {name: 'bob'}, {foo: 42}] 59 | const output = [{name: 'alice'}, {name: 'bob'}] 60 | solve(input, output) 61 | // tries R.map(R.has('foo')) 62 | // tries R.map(R.has('name')) 63 | // tries R.map(R.has('bar')) 64 | // tries R.map(R.has('alice')) 65 | // ... 66 | ``` 67 | 68 | Hope others can contribute to this effort, since I know nothing about symbolic computation and 69 | automatic solvers. Rambo is my attempt at brute forcing a problem with a small solution set. 70 | 71 | ## Details 72 | 73 | You can provide multiple input / output pairs 74 | 75 | ```js 76 | const solution = solve([ 77 | [input1, output1], 78 | [input2, output2], 79 | ... 80 | ]) 81 | ``` 82 | 83 | This is useful because sometimes there might be multiple solutions for a single input / output 84 | pair. 85 | 86 | [![NPM][npm-icon] ][npm-url] 87 | 88 | [![Build status][ci-image] ][ci-url] 89 | [![semantic-release][semantic-image] ][semantic-url] 90 | [![js-standard-style][standard-image]][standard-url] 91 | 92 | ### Small print 93 | 94 | Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2016 95 | 96 | 97 | * [@bahmutov](https://twitter.com/bahmutov) 98 | * [glebbahmutov.com](http://glebbahmutov.com) 99 | * [blog](http://glebbahmutov.com/blog) 100 | 101 | 102 | License: MIT - do anything with the code, but don't blame me if it does not work. 103 | 104 | Support: if you find any problems with this module, email / tweet / 105 | [open issue](https://github.com/bahmutov/rambo/issues) on Github 106 | 107 | ## MIT License 108 | 109 | Copyright (c) 2016 Gleb Bahmutov <gleb.bahmutov@gmail.com> 110 | 111 | Permission is hereby granted, free of charge, to any person 112 | obtaining a copy of this software and associated documentation 113 | files (the "Software"), to deal in the Software without 114 | restriction, including without limitation the rights to use, 115 | copy, modify, merge, publish, distribute, sublicense, and/or sell 116 | copies of the Software, and to permit persons to whom the 117 | Software is furnished to do so, subject to the following 118 | conditions: 119 | 120 | The above copyright notice and this permission notice shall be 121 | included in all copies or substantial portions of the Software. 122 | 123 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 124 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 125 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 126 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 127 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 128 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 129 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 130 | OTHER DEALINGS IN THE SOFTWARE. 131 | 132 | [npm-icon]: https://nodei.co/npm/rambo.png?downloads=true 133 | [npm-url]: https://npmjs.org/package/rambo 134 | [ci-image]: https://travis-ci.org/bahmutov/rambo.png?branch=master 135 | [ci-url]: https://travis-ci.org/bahmutov/rambo 136 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 137 | [semantic-url]: https://github.com/semantic-release/semantic-release 138 | [standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg 139 | [standard-url]: http://standardjs.com/ 140 | --------------------------------------------------------------------------------