<commit_before>var argv = require('optimist').argv;

exports.cli = (function() {

  if (argv.rif - 5 * argv.xup > 7.138) {
      console.log('Buy more riffiwobbles');
  }
  else {
      console.log('Sell the xupptumblers');
  }

})();
<commit_msg>implemented the parsing of arguments in README.md<commit_after>var optimist = require('optimist')
    , argv = optimist.argv
    , _ = require('underscore')
    ;

exports.cli = (function() {

  optimist
    .usage('query or submit commands from terminal.\nUsage: $0')
    .argv
  ;

  optimist
    .alias('a', 'add')
    .describe('a', 'open $EDITOR to edit and add last command')
    .argv
  ;
  if ( argv.a || argv.add ) addCommand()

  optimist
    .alias('e', 'exec')
    .describe('e', 'exec the command with a given id')
    .check(function () {
      execId = argv.e || argv.exec;
      if(execId) {
        execId = parseInt(execId, 10);
        if ( _.isNaN(execId) )
          throw 'you need to specify valid id for command to be executed.';
      };
    })
    .argv
  ;
  if ( argv.e || argv.exec ) execCommand()

  optimist
    .alias('q', 'query')
    .describe('q', 'query for commands containing the keywords.')
    .argv
  ;
  if ( argv.q || argv.query ) queryCommand()

  optimist
    .alias('p', 'paste')
    .describe('p', 'paste in the command with given id.')
    .argv
  ;
  if ( argv.p || argv.paste ) pasteCommand()

  function addCommand () {
    console.log("addCommand Executed.");
  }

  function execCommand () {
    console.log("execCommand Executed.");
  }

  function queryCommand () {
    console.log("queryCommand Executed.");
  }

  function pasteCommand () {
    console.log("pasteCommand Executed.");
  }

})();
<|endoftext|>


<commit_before>const sinon = require('sinon');
const unexpected = require('unexpected');
const baseExpect = unexpected.clone();

// pull in plugin for -sinon assertions
baseExpect.use(require('unexpected-sinon'));

baseExpect.addType({
    name: 'jestMock',
    base: 'function',
    identify: value => typeof value === 'function' && value._isMockFunction
});

baseExpect.addAssertion(
    '<string> to jest match <string>',
    (expect, subject, value) => {
        expect.errorMode = 'bubble';
        return expect(subject, 'to contain', value);
    }
);

baseExpect.addAssertion(
    '<string> to jest match <regexp>',
    (expect, subject, value) => {
        expect.errorMode = 'bubble';
        return expect(subject, 'to match', value);
    }
);

baseExpect.addAssertion(
    '<any> not to be null or undefined',
    (expect, subject) => {
        expect.errorMode = 'bubble';

        return expect.promise
            .props({
                null: expect(subject, 'not to be null')
                    .catch(e => e)
                    .then(() => null),
                undefined: expect(subject, 'not to be undefined')
                    .catch(e => e)
                    .then(() => null)
            })
            .then(results => {
                var passNull = results.null === null;
                var passUndefined = results.undefined === null;

                if (passNull && passUndefined) {
                    // neither failed so we succeed
                    return;
                }

                if (!passNull && !passUndefined) {
                    throw results.null;
                }

                throw results.undefined;
            });
    }
);

class CustomSpec {
    constructor(spec, value, isTopLevel = true) {
        this.spec = spec;
        this.value = value;
        this.nested = !isTopLevel;
    }
}

function buildCustomSpecWrapper(Type) {
    return value => {
        return new Type(value, undefined, false);
    };
}

function registerUnexpectedTypeForCustomSpec(Type, options = {}) {
    baseExpect.addType({
        name: Type.name,
        base: 'object',
        identify: value => value instanceof Type,
        inspect:
            options.inspect !== undefined
                ? options.inspect
                : ({ spec, nested }, depth, output, inspect) => {
                      if (nested) output.text(`${Type.name}(`);
                      if (spec !== undefined) {
                          output.append(inspect(spec, depth));
                      }
                      if (nested) output.text(')');
                  }
    });
}

function isRegExp(value) {
    return Object.prototype.toString.call(value) === '[object RegExp]';
}

function unpackNestedSpec(expect, { args, spec }) {
    return args !== undefined ? expect.it(...args) : spec;
}

function unpackNestedSpecs(expect, spec) {
    if (spec instanceof CustomSpec) spec = unpackNestedSpec(expect, spec);

    if (Array.isArray(spec)) {
        spec = spec.map(item => {
            if (item instanceof CustomSpec) {
                return unpackNestedSpecs(expect, item);
            } else {
                return item;
            }
        });
    } else if (typeof spec === 'object' && spec !== null && !isRegExp(spec)) {
        spec = Object.assign({}, spec);
        Object.keys(spec).forEach(key => {
            const value = spec[key];
            if (value instanceof CustomSpec) {
                spec[key] = unpackNestedSpecs(expect, value);
            }
        });
    }

    return spec;
}

function anyTypeToString(type) {
    switch (type) {
        case Array:
            return 'array';
        case Function:
            return 'function';
        case Number:
            // XXX workaround expect(1, 'to be a', Number) failing
            return 'number';
        case String:
            return 'string';
        default:
            return null;
    }
}

function inspectWithNativeConstructor(spec, depth, inspect) {
    const anyTypeString = anyTypeToString(spec);
    if (anyTypeString !== null || (typeof spec === 'function' && spec.name)) {
        return spec.name;
    } else {
        return inspect(spec, depth);
    }
}

class AnySpec extends CustomSpec {
    get args() {
        const { spec } = this;
        const anyTypeString = anyTypeToString(spec);
        if (anyTypeString !== null) {
            const a = anyTypeString === 'array' ? 'an' : 'a';
            return [`to be ${a} ${anyTypeString}`];
        } else {
            return ['to be a', spec];
        }
    }
}
registerUnexpectedTypeForCustomSpec(AnySpec, {
    inspect: ({ spec, nested }, depth, output, inspect) => {
        if (nested) output.text(`AnySpec(`);
        output.append(inspectWithNativeConstructor(spec, depth, inspect));
        if (nested) output.text(')');
    }
});

baseExpect.addAssertion(
    '<any> to equal <AnySpec>',
    (expect, subject, anySpec) => {
        expect.errorMode = 'bubble';
        return expect(subject, ...anySpec.args);
    }
);

class AnythingSpec extends CustomSpec {
    get args() {
        return ['not to be null or undefined'];
    }
}
registerUnexpectedTypeForCustomSpec(AnythingSpec);

class ArrayContainingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(ArrayContainingSpec);

function assertContainingArray(subject, spec) {
    // create a containing assertion and flatten out the elements to check for
    return [subject, 'to contain'].concat(spec);
}

function isPresentIn(element, subject) {
    return subject.indexOf(element) > -1;
}

baseExpect.addAssertion(
    '<array> to equal <ArrayContainingSpec>',
    (expect, subject, { spec }) => {
        spec = unpackNestedSpecs(expect, spec);

        // check if we are operating on a subset
        if (subject.length === spec.length) {
            // prefer "to satisfy" since we are not
            return expect(subject, 'to satisfy', spec);
        }

        try {
            // XXX workaround having no direct containing assertion
            expect.apply(expect, assertContainingArray(subject, spec));
        } catch (e) {
            const present = spec.filter(element =>
                isPresentIn(element, subject)
            );

            expect(present, 'to equal', spec);
        }
    }
);

class CalledWithSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(CalledWithSpec);

baseExpect.addAssertion(
    '<jestMock> to have been called with <CalledWithSpec>',
    (expect, subject, calledWithSpec) => {
        const jestMock = subject.mock;
        const sinonSpy = sinon.spy();

        jestMock.calls.forEach(callArgs => {
            sinonSpy(...callArgs);
        });

        expect.errorMode = 'bubble';
        expect(sinonSpy, 'to have been called with', calledWithSpec);
    }
);

baseExpect.addAssertion(
    '<spy> to have been called with <CalledWithSpec>',
    (expect, subject, { spec }) => {
        const callArgs = spec.map(v => unpackNestedSpecs(expect, v));

        expect(subject, 'to have a call satisfying', callArgs);
    }
);

class ObjectContainingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(ObjectContainingSpec);

baseExpect.addAssertion(
    '<object> to equal <ObjectContainingSpec>',
    (expect, subject, { spec, nested }) => {
        spec = unpackNestedSpecs(expect, spec);

        if (!nested) expect.errorMode = 'bubble';
        return expect(subject, 'to satisfy', spec);
    }
);

class StringContainingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(StringContainingSpec);

baseExpect.addAssertion(
    '<string> to equal <StringContainingSpec>',
    (expect, subject, { spec }) => {
        return expect(subject, 'to contain', spec);
    }
);

class IdentitySpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(IdentitySpec);

baseExpect.addAssertion(
    '<array> to contain <IdentitySpec>',
    (expect, subject, { spec }) => {
        expect.errorMode = 'diff';
        return expect(subject, 'to have an item satisfying to be', spec);
    }
);

class KeyPathSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(KeyPathSpec);

function keyPathToNestedObjects(keyPathString, keyPathValue) {
    const keyPath = keyPathString.split('.');
    // the innermost property will point to a value
    let nestedObjects = keyPathValue;

    while (keyPath.length > 0) {
        const property = keyPath.pop();
        // nest previous value within a new object
        nestedObjects = {
            [property]: nestedObjects
        };
    }

    return nestedObjects;
}

baseExpect.addAssertion(
    '<object> to have property <KeyPathSpec>',
    (expect, subject, { spec, value }) => {
        if (value === undefined) {
            value = expect.it('to be defined');
        }

        expect.errorMode = 'default';
        return expect(
            subject,
            'to satisfy',
            keyPathToNestedObjects(spec, value)
        );
    }
);

baseExpect.addAssertion(
    '<object> to satisfy <KeyPathSpec>',
    (expect, subject, { spec, value }) => {
        if (value === undefined) {
            value = expect.it('to be defined');
        }

        expect.errorMode = 'default';
        return expect(
            subject,
            'to satisfy',
            keyPathToNestedObjects(spec, value)
        );
    }
);

class StringMatchingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(StringMatchingSpec);

baseExpect.addAssertion(
    '<string> to equal <StringMatchingSpec>',
    (expect, subject, { spec }) => {
        return expect(subject, 'to match', spec);
    }
);

function defaultFlags(assertion, flags) {
    return flags.not ? `not ${assertion}` : assertion;
}

module.exports = function expect(subject, ...rest) {
    if (rest.length > 0) {
        throw new Error('Expect takes at most one argument.');
    }

    const expect = baseExpect;
    const flags = {
        not: false
    };
    let _promise = null;

    const _buildAssertion = (assertion, options = {}) => {
        const {
            numberOfArgs = 1,
            withFlags = defaultFlags,
            wrapValue = v => v
        } = options;

        if (numberOfArgs === 0) {
            return () => expect(subject, withFlags(assertion, flags));
        } else if (numberOfArgs > 1) {
            return (...args) =>
                expect(subject, withFlags(assertion, flags), wrapValue(args));
        }

        // single argument case (most common)
        return value =>
            expect(subject, withFlags(assertion, flags), wrapValue(value));
    };

    const buildAssertion = (assertion, options) => {
        const assertionFunction = _buildAssertion(assertion, options);

        return (...args) => {
            if (_promise !== null) {
                return _promise.then(() => assertionFunction(...args));
            } else {
                return assertionFunction(...args);
            }
        }
    };

    const buildAssertionSomeArgs = assertion => {
        const oneArg = buildAssertion(assertion);
        const noArgs = buildAssertion(assertion, { numberOfArgs: 0 });

        return value => (value === undefined ? noArgs() : oneArg(value));
    };

    const assertions = {
        toBe: buildAssertion('to be'),
        toBeCloseTo: buildAssertion('to be close to'),
        toBeDefined: buildAssertion('to be defined', { numberOfArgs: 0 }),
        toBeFalsy: buildAssertion('to be falsy', { numberOfArgs: 0 }),
        toBeGreaterThan: buildAssertion('to be greater than'),
        toBeGreaterThanOrEqual: buildAssertion(
            'to be greater than or equal to'
        ),
        toBeInstanceOf: buildAssertion('to be a'),
        toBeLessThan: buildAssertion('to be less than'),
        toBeLessThanOrEqual: buildAssertion('to be less than or equal to'),
        toBeNull: buildAssertion('to be null', { numberOfArgs: 0 }),
        toBeTruthy: buildAssertion('to be truthy', { numberOfArgs: 0 }),
        toContain: buildAssertion('to contain', {
            wrapValue: value => new IdentitySpec(value)
        }),
        toContainEqual: buildAssertion('to contain'),
        toEqual: buildAssertion('to equal'),
        toHaveBeenCalled: buildAssertion('was called', {
            numberOfArgs: 0,
            withFlags: assertion => {
                return flags.not ? 'was not called' : assertion;
            }
        }),
        toHaveBeenCalledTimes: buildAssertion('was called times'),
        toHaveBeenCalledWith: buildAssertion('to have been called with', {
            numberOfArgs: Infinity,
            wrapValue: args => new CalledWithSpec(args)
        }),
        toHaveLength: buildAssertion('to have length'),
        toHaveProperty: buildAssertion('to have property', {
            numberOfArgs: 2,
            wrapValue: args => new KeyPathSpec(args[0], args[1])
        }),
        toMatch: buildAssertion('to jest match'),
        toMatchObject: buildAssertion('to equal', {
            wrapValue: value => new ObjectContainingSpec(value)
        }),
        toMatchSnapshot: () => {
            throw new Error(
                'jest-unexpected: toMatchSnapshot() is not supported.'
            );
        },
        toThrow: buildAssertionSomeArgs('to throw'),
        toThrowErrorMatchingSnapshot: () => {
            throw new Error(
                'jest-unexpected: toThrowErrorMatchingSnapshot() is not supported.'
            );
        }
    };

    const createChainedAssertionPromiseForState = (promiseState, toSubject) => {
        _promise = expect.promise(() => {
            return expect(subject, `to be ${promiseState} with`, result => {
                subject = toSubject(result);
            });
        });
        // attach the assertion methods to allow chaining on the output value
        Object.assign(_promise, assertions);
        // attach the negation operator
        Object.defineProperty(_promise, 'not', {
            get: () => {
                flags.not = true;
                return _promise;
            }
        });
        // return the promise so these calls can be awaited
        return _promise;
    };

    return Object.assign({
        get not() {
            flags.not = true;
            return this;
        },
        get resolves() {
            return createChainedAssertionPromiseForState(
                "fulfilled",
                result => result
            );
        },
        get rejects() {
            return createChainedAssertionPromiseForState(
                "rejected",
                result => result !== undefined ? result.message : undefined
            );
        }
    }, assertions);
};

module.exports.addSnapshotSerializer = () => {
    throw new Error(
        'jest-unexpected: expect.addSnapshotSerializer() is not supported.'
    );
};
module.exports.any = buildCustomSpecWrapper(AnySpec);
module.exports.anything = buildCustomSpecWrapper(AnythingSpec);
module.exports.arrayContaining = buildCustomSpecWrapper(ArrayContainingSpec);
module.exports.extend = () => {
    throw new Error('jest-unexpected: expect.extend() is not supported.');
};
module.exports.objectContaining = buildCustomSpecWrapper(ObjectContainingSpec);
module.exports.stringContaining = buildCustomSpecWrapper(StringContainingSpec);
module.exports.stringMatching = buildCustomSpecWrapper(StringMatchingSpec);
// attach Unexpected output object to allow controlling e.g. terminal width
module.exports.output = baseExpect.output;
<commit_msg>Implement to special case for .resolves.toThrow().<commit_after>const sinon = require('sinon');
const unexpected = require('unexpected');
const baseExpect = unexpected.clone();

// pull in plugin for -sinon assertions
baseExpect.use(require('unexpected-sinon'));

baseExpect.addType({
    name: 'jestMock',
    base: 'function',
    identify: value => typeof value === 'function' && value._isMockFunction
});

baseExpect.addAssertion(
    '<string> to jest match <string>',
    (expect, subject, value) => {
        expect.errorMode = 'bubble';
        return expect(subject, 'to contain', value);
    }
);

baseExpect.addAssertion(
    '<string> to jest match <regexp>',
    (expect, subject, value) => {
        expect.errorMode = 'bubble';
        return expect(subject, 'to match', value);
    }
);

baseExpect.addAssertion(
    '<any> not to be null or undefined',
    (expect, subject) => {
        expect.errorMode = 'bubble';

        return expect.promise
            .props({
                null: expect(subject, 'not to be null')
                    .catch(e => e)
                    .then(() => null),
                undefined: expect(subject, 'not to be undefined')
                    .catch(e => e)
                    .then(() => null)
            })
            .then(results => {
                var passNull = results.null === null;
                var passUndefined = results.undefined === null;

                if (passNull && passUndefined) {
                    // neither failed so we succeed
                    return;
                }

                if (!passNull && !passUndefined) {
                    throw results.null;
                }

                throw results.undefined;
            });
    }
);

class CustomSpec {
    constructor(spec, value, isTopLevel = true) {
        this.spec = spec;
        this.value = value;
        this.nested = !isTopLevel;
    }
}

function buildCustomSpecWrapper(Type) {
    return value => {
        return new Type(value, undefined, false);
    };
}

function registerUnexpectedTypeForCustomSpec(Type, options = {}) {
    baseExpect.addType({
        name: Type.name,
        base: 'object',
        identify: value => value instanceof Type,
        inspect:
            options.inspect !== undefined
                ? options.inspect
                : ({ spec, nested }, depth, output, inspect) => {
                      if (nested) output.text(`${Type.name}(`);
                      if (spec !== undefined) {
                          output.append(inspect(spec, depth));
                      }
                      if (nested) output.text(')');
                  }
    });
}

function isRegExp(value) {
    return Object.prototype.toString.call(value) === '[object RegExp]';
}

function unpackNestedSpec(expect, { args, spec }) {
    return args !== undefined ? expect.it(...args) : spec;
}

function unpackNestedSpecs(expect, spec) {
    if (spec instanceof CustomSpec) spec = unpackNestedSpec(expect, spec);

    if (Array.isArray(spec)) {
        spec = spec.map(item => {
            if (item instanceof CustomSpec) {
                return unpackNestedSpecs(expect, item);
            } else {
                return item;
            }
        });
    } else if (typeof spec === 'object' && spec !== null && !isRegExp(spec)) {
        spec = Object.assign({}, spec);
        Object.keys(spec).forEach(key => {
            const value = spec[key];
            if (value instanceof CustomSpec) {
                spec[key] = unpackNestedSpecs(expect, value);
            }
        });
    }

    return spec;
}

function anyTypeToString(type) {
    switch (type) {
        case Array:
            return 'array';
        case Function:
            return 'function';
        case Number:
            // XXX workaround expect(1, 'to be a', Number) failing
            return 'number';
        case String:
            return 'string';
        default:
            return null;
    }
}

function inspectWithNativeConstructor(spec, depth, inspect) {
    const anyTypeString = anyTypeToString(spec);
    if (anyTypeString !== null || (typeof spec === 'function' && spec.name)) {
        return spec.name;
    } else {
        return inspect(spec, depth);
    }
}

class AnySpec extends CustomSpec {
    get args() {
        const { spec } = this;
        const anyTypeString = anyTypeToString(spec);
        if (anyTypeString !== null) {
            const a = anyTypeString === 'array' ? 'an' : 'a';
            return [`to be ${a} ${anyTypeString}`];
        } else {
            return ['to be a', spec];
        }
    }
}
registerUnexpectedTypeForCustomSpec(AnySpec, {
    inspect: ({ spec, nested }, depth, output, inspect) => {
        if (nested) output.text(`AnySpec(`);
        output.append(inspectWithNativeConstructor(spec, depth, inspect));
        if (nested) output.text(')');
    }
});

baseExpect.addAssertion(
    '<any> to equal <AnySpec>',
    (expect, subject, anySpec) => {
        expect.errorMode = 'bubble';
        return expect(subject, ...anySpec.args);
    }
);

class AnythingSpec extends CustomSpec {
    get args() {
        return ['not to be null or undefined'];
    }
}
registerUnexpectedTypeForCustomSpec(AnythingSpec);

class ArrayContainingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(ArrayContainingSpec);

function assertContainingArray(subject, spec) {
    // create a containing assertion and flatten out the elements to check for
    return [subject, 'to contain'].concat(spec);
}

function isPresentIn(element, subject) {
    return subject.indexOf(element) > -1;
}

baseExpect.addAssertion(
    '<array> to equal <ArrayContainingSpec>',
    (expect, subject, { spec }) => {
        spec = unpackNestedSpecs(expect, spec);

        // check if we are operating on a subset
        if (subject.length === spec.length) {
            // prefer "to satisfy" since we are not
            return expect(subject, 'to satisfy', spec);
        }

        try {
            // XXX workaround having no direct containing assertion
            expect.apply(expect, assertContainingArray(subject, spec));
        } catch (e) {
            const present = spec.filter(element =>
                isPresentIn(element, subject)
            );

            expect(present, 'to equal', spec);
        }
    }
);

class CalledWithSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(CalledWithSpec);

baseExpect.addAssertion(
    '<jestMock> to have been called with <CalledWithSpec>',
    (expect, subject, calledWithSpec) => {
        const jestMock = subject.mock;
        const sinonSpy = sinon.spy();

        jestMock.calls.forEach(callArgs => {
            sinonSpy(...callArgs);
        });

        expect.errorMode = 'bubble';
        expect(sinonSpy, 'to have been called with', calledWithSpec);
    }
);

baseExpect.addAssertion(
    '<spy> to have been called with <CalledWithSpec>',
    (expect, subject, { spec }) => {
        const callArgs = spec.map(v => unpackNestedSpecs(expect, v));

        expect(subject, 'to have a call satisfying', callArgs);
    }
);

class ObjectContainingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(ObjectContainingSpec);

baseExpect.addAssertion(
    '<object> to equal <ObjectContainingSpec>',
    (expect, subject, { spec, nested }) => {
        spec = unpackNestedSpecs(expect, spec);

        if (!nested) expect.errorMode = 'bubble';
        return expect(subject, 'to satisfy', spec);
    }
);

class StringContainingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(StringContainingSpec);

baseExpect.addAssertion(
    '<string> to equal <StringContainingSpec>',
    (expect, subject, { spec }) => {
        return expect(subject, 'to contain', spec);
    }
);

class IdentitySpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(IdentitySpec);

baseExpect.addAssertion(
    '<array> to contain <IdentitySpec>',
    (expect, subject, { spec }) => {
        expect.errorMode = 'diff';
        return expect(subject, 'to have an item satisfying to be', spec);
    }
);

class KeyPathSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(KeyPathSpec);

function keyPathToNestedObjects(keyPathString, keyPathValue) {
    const keyPath = keyPathString.split('.');
    // the innermost property will point to a value
    let nestedObjects = keyPathValue;

    while (keyPath.length > 0) {
        const property = keyPath.pop();
        // nest previous value within a new object
        nestedObjects = {
            [property]: nestedObjects
        };
    }

    return nestedObjects;
}

baseExpect.addAssertion(
    '<object> to have property <KeyPathSpec>',
    (expect, subject, { spec, value }) => {
        if (value === undefined) {
            value = expect.it('to be defined');
        }

        expect.errorMode = 'default';
        return expect(
            subject,
            'to satisfy',
            keyPathToNestedObjects(spec, value)
        );
    }
);

baseExpect.addAssertion(
    '<object> to satisfy <KeyPathSpec>',
    (expect, subject, { spec, value }) => {
        if (value === undefined) {
            value = expect.it('to be defined');
        }

        expect.errorMode = 'default';
        return expect(
            subject,
            'to satisfy',
            keyPathToNestedObjects(spec, value)
        );
    }
);

class StringMatchingSpec extends CustomSpec {}
registerUnexpectedTypeForCustomSpec(StringMatchingSpec);

baseExpect.addAssertion(
    '<string> to equal <StringMatchingSpec>',
    (expect, subject, { spec }) => {
        return expect(subject, 'to match', spec);
    }
);

function defaultFlags(assertion, flags) {
    return flags.not ? `not ${assertion}` : assertion;
}

module.exports = function expect(subject, ...rest) {
    if (rest.length > 0) {
        throw new Error('Expect takes at most one argument.');
    }

    const expect = baseExpect;
    const flags = {
        not: false
    };
    let _promise = null;

    const _buildAssertion = (assertion, options = {}) => {
        const {
            numberOfArgs = 1,
            withFlags = defaultFlags,
            wrapValue = v => v
        } = options;

        if (numberOfArgs === 0) {
            return () => expect(subject, withFlags(assertion, flags));
        } else if (numberOfArgs > 1) {
            return (...args) =>
                expect(subject, withFlags(assertion, flags), wrapValue(args));
        }

        // single argument case (most common)
        return value =>
            expect(subject, withFlags(assertion, flags), wrapValue(value));
    };

    const buildAssertion = (assertion, options) => {
        const assertionFunction = _buildAssertion(assertion, options);

        return (...args) => {
            if (_promise !== null) {
                return _promise.then(() => assertionFunction(...args));
            } else {
                return assertionFunction(...args);
            }
        }
    };

    const buildAssertionSomeArgs = assertion => {
        const oneArg = buildAssertion(assertion);
        const noArgs = buildAssertion(assertion, { numberOfArgs: 0 });

        return value => (value === undefined ? noArgs() : oneArg(value));
    };

    const assertions = {
        toBe: buildAssertion('to be'),
        toBeCloseTo: buildAssertion('to be close to'),
        toBeDefined: buildAssertion('to be defined', { numberOfArgs: 0 }),
        toBeFalsy: buildAssertion('to be falsy', { numberOfArgs: 0 }),
        toBeGreaterThan: buildAssertion('to be greater than'),
        toBeGreaterThanOrEqual: buildAssertion(
            'to be greater than or equal to'
        ),
        toBeInstanceOf: buildAssertion('to be a'),
        toBeLessThan: buildAssertion('to be less than'),
        toBeLessThanOrEqual: buildAssertion('to be less than or equal to'),
        toBeNull: buildAssertion('to be null', { numberOfArgs: 0 }),
        toBeTruthy: buildAssertion('to be truthy', { numberOfArgs: 0 }),
        toContain: buildAssertion('to contain', {
            wrapValue: value => new IdentitySpec(value)
        }),
        toContainEqual: buildAssertion('to contain'),
        toEqual: buildAssertion('to equal'),
        toHaveBeenCalled: buildAssertion('was called', {
            numberOfArgs: 0,
            withFlags: assertion => {
                return flags.not ? 'was not called' : assertion;
            }
        }),
        toHaveBeenCalledTimes: buildAssertion('was called times'),
        toHaveBeenCalledWith: buildAssertion('to have been called with', {
            numberOfArgs: Infinity,
            wrapValue: args => new CalledWithSpec(args)
        }),
        toHaveLength: buildAssertion('to have length'),
        toHaveProperty: buildAssertion('to have property', {
            numberOfArgs: 2,
            wrapValue: args => new KeyPathSpec(args[0], args[1])
        }),
        toMatch: buildAssertion('to jest match'),
        toMatchObject: buildAssertion('to equal', {
            wrapValue: value => new ObjectContainingSpec(value)
        }),
        toMatchSnapshot: () => {
            throw new Error(
                'jest-unexpected: toMatchSnapshot() is not supported.'
            );
        },
        toThrow: buildAssertionSomeArgs('to throw'),
        toThrowErrorMatchingSnapshot: () => {
            throw new Error(
                'jest-unexpected: toThrowErrorMatchingSnapshot() is not supported.'
            );
        }
    };

    const createChainedAssertionPromiseForState = (promiseState, toSubject) => {
        _promise = expect.promise(() => {
            return expect(subject, `to be ${promiseState} with`, result => {
                subject = toSubject(result);
            });
        });
        // attach the assertion methods to allow chaining on the output value
        Object.assign(_promise, assertions);
        // attach the negation operator
        Object.defineProperty(_promise, 'not', {
            get: () => {
                flags.not = true;
                return _promise;
            }
        });
        // return the promise so these calls can be awaited
        return _promise;
    };

    return Object.assign({
        get not() {
            flags.not = true;
            return this;
        },
        get resolves() {
            const resolves = createChainedAssertionPromiseForState(
                "fulfilled",
                result => result
            );
            // override "to throw" using "to error" to allow .resolves.toThrow()
            resolves.toThrow = () => expect(() => resolves, 'to error');
            return resolves;
        },
        get rejects() {
            return createChainedAssertionPromiseForState(
                "rejected",
                result => result !== undefined ? result.message : undefined
            );
        }
    }, assertions);
};

module.exports.addSnapshotSerializer = () => {
    throw new Error(
        'jest-unexpected: expect.addSnapshotSerializer() is not supported.'
    );
};
module.exports.any = buildCustomSpecWrapper(AnySpec);
module.exports.anything = buildCustomSpecWrapper(AnythingSpec);
module.exports.arrayContaining = buildCustomSpecWrapper(ArrayContainingSpec);
module.exports.extend = () => {
    throw new Error('jest-unexpected: expect.extend() is not supported.');
};
module.exports.objectContaining = buildCustomSpecWrapper(ObjectContainingSpec);
module.exports.stringContaining = buildCustomSpecWrapper(StringContainingSpec);
module.exports.stringMatching = buildCustomSpecWrapper(StringMatchingSpec);
// attach Unexpected output object to allow controlling e.g. terminal width
module.exports.output = baseExpect.output;
<|endoftext|>


<commit_before>// v0.1.0
function knucklebone(t,e){var s=Object.create(KBP);return t&&(e&&e===!0?s.form.submit=t:t()),s}var KBP={form:{},rqst:function(t,e,s,r){var n=new XMLHttpRequest,o=this;n.addEventListener("readystatechange",function(){4===n.readyState&&e(o.res(this))}),n.open(s,t),r?n.send(r):n.send()},res:function(t){var e={};return e.json=JSON.parse(t.response),e.response=t.response,e.responseType=t.responseType,e.responseURL=t.responseURL,e.status=t.status,e.statusText=t.statusText,e.success=e.status>=200&&e.status<300?!0:!1,e},get:function(t,e){this.rqst(t,e,"GET")},post:function(t,e,s){var r=this;r.form.data=e,r.form.data.addEventListener("submit",function(n){n.preventDefault(),null!=r.form.submit&&r.form.submit(),r.form.errors!==!0&&r.rqst(t,s,"POST",r.formify(e))})},formify:function(t){var e=new FormData(t);return e}};<commit_msg>Remove version number<commit_after>function knucklebone(t,e){var s=Object.create(KBP);return t&&(e&&e===!0?s.form.submit=t:t()),s}var KBP={form:{},rqst:function(t,e,s,r){var n=new XMLHttpRequest,o=this;n.addEventListener("readystatechange",function(){4===n.readyState&&e(o.res(this))}),n.open(s,t),r?n.send(r):n.send()},res:function(t){var e={};return e.json=JSON.parse(t.response),e.response=t.response,e.responseType=t.responseType,e.responseURL=t.responseURL,e.status=t.status,e.statusText=t.statusText,e.success=e.status>=200&&e.status<300?!0:!1,e},get:function(t,e){this.rqst(t,e,"GET")},post:function(t,e,s){var r=this;r.form.data=e,r.form.data.addEventListener("submit",function(n){n.preventDefault(),null!=r.form.submit&&r.form.submit(),r.form.errors!==!0&&r.rqst(t,s,"POST",r.formify(e))})},formify:function(t){var e=new FormData(t);return e}};
<|endoftext|>


<commit_before>// Entry point for the library. Implements the bot object,
// get / set, lifecycle management, API calling and the
// `getUpdates` loop.
var util = require("util");
var utils = require("./utils");
var stream = require("stream");
var EventEmitter = require("events");
var FormData = require("form-data");

var model = require("./model");
var misc = require("./misc");
var message = require("./message");
var inline = require("./inline");
var reply = require("./reply");

// TODO
function NetworkError(err) { this.err = err; }
function TelegramError(err) { this.err = err; }

function Bot(authToken, options) {
  if (!(this instanceof Bot)) return new Bot(authToken, options);
  EventEmitter.call(this);

  if (options === undefined) options = {};
  if (!options.hasOwnProperty("timeout")) options.timeout = 10 * 60;
  if (!options.hasOwnProperty("strict")) options.strict = true; //FIXME: use, set to false
  if (!options.hasOwnProperty("actionInterval")) options.actionInterval = 3000;
  if (!options.hasOwnProperty("autodetect")) options.autodetect = true;
  if (!options.hasOwnProperty("retryInterval")) options.retryInterval = 2000;
  if (!options.hasOwnProperty("immediate")) options.immediate = false;

  if (!utils.isStr(authToken)) throw new Error("Invalid auth token specified");

  this.options = options;
  this.authToken = authToken;
  this.data = {};

  message.initMessage.call(this);
  inline.initInline.call(this);
  misc.initMisc.call(this);
  reply.initReply.call(this);

  this.on("ready", startLoop);
  process.nextTick(firstDetection.bind(this));
}

util.inherits(Bot, EventEmitter);

Bot.prototype.get = function get(name) {
  return this.data[name];
};

Bot.prototype.set = function set(name, value) {
  this.data[name] = value;
};

// FIXME: wrap errors of form, req and res in NetworkError
Bot.prototype.callMethod = function (method, parameters, cb) {
  if (cb === undefined) cb = parameters, parameters = {};

  // Prepare the body
  var form = new FormData(), setPlaceholder = true;
  form.on("error", handleResponse);
  Object.keys(parameters).forEach(function (key) {
    var value = parameters[key];
    if (value === null || value === undefined) return;
    setPlaceholder = false;

    if (utils.isNum(value) || utils.isBool(value) || utils.isStr(value))
      return form.append(key, value.toString());
    if (utils.isBuffer(value) || utils.isReadable(value))
      return form.append(key, value, value.options);
    form.append(key, JSON.stringify(value));
  });
  if (setPlaceholder) form.append("placeholder", "placeholder");

  // Make the request
  form.submit({
    agent: this.options.agent,
    protocol: "https:",
    host: "api.telegram.org",
    path: "/bot" + this.authToken + "/" + method,
    headers: { accept: "application/json" }
  }, handleResponse);

  // Parse the response
  function handleResponse(err, res) {
    if (err) return cb(new NetworkError(err));

    if (res.headers["content-type"] !== "application/json") {
      res.resume();
      return cb(new NetworkError("API response not in JSON"));
    }

    // FIXME: not the most efficient way to collect output
    var body = Buffer(0);
    res.on("error", handleResponse);
    res.on("readable", function () {
      var chunk = res.read();
      if (chunk) body = Buffer.concat([body, chunk]);
    });
    res.on("end", function () { handleBody(body, res.statusCode); });
  }

  function handleBody(body, status) {
    try {
      body = JSON.parse(body);
    } catch (err) { return cb(err); }
    if (!utils.isBool(body.ok))
      return cb(new NetworkError("ok field not a boolean in API response"));
    if (body.ok !== (status === 200))
      return cb(new NetworkError("ok field not matching JSON response"));
    if (body.ok) return cb(null, body.result);
    cb(new TelegramError(body));
  }
};

Bot.prototype.autodetect = function autodetect(cb) {
  if (!cb) cb = function () {};
  this.callMethod("getMe", function (err, result) {
    if (err) return cb(err);

    this.set("id", result.id);
    this.set("username", result.username);
    this.set("firstname", result.first_name);
    this.set("lastname", result.last_name);
    cb();
  }.bind(this));
};

function firstDetection() {
  if (this.stopped) return;

  if (this.options.autodetect)
    return this.autodetect(function (err) {
      if (err) {
        this.emit("error", err);
        return setTimeout(firstDetection.bind(this), this.options.retryInterval);
      }
      this.emit("ready");
    }.bind(this));

  if (!utils.isInt(this.get("id"))) {
    console.error("Bot was not initialized!");
    process.exit(1);
  }
  this.emit("ready");
}

function startLoop() {
  if (this.stopped) return;

  this.callMethod("getUpdates", {
    offset: this.offset,
    timeout: this.firstCycleDone ? this.options.timeout : 0,
  }, function (err, result) {
    if (err) {
      this.emit("error", err);
      return setTimeout(startLoop.bind(this), this.options.retryInterval);
    }

    result.forEach(this.processUpdate.bind(this));

    if (!this.firstCycleDone && result.length == 0) {
      this.firstCycleDone = true;
      this.emit("synced");
    }
    startLoop.call(this);
  }.bind(this));
}

Bot.prototype.ready = function ready(cb) {
  if (utils.isInt(this.get("id")))
    return cb();
  this.on("ready", cb);
};

Bot.prototype.synced = function synced(cb) {
  if (this.firstCycleDone)
    return cb();
  this.on("synced", cb);
};

Bot.prototype.stop = function stop() {
  if (this.stopped) return;
  this.stopped = true;
  this.emit("stopped");
};

Bot.prototype.processUpdate = function processUpdate(update) {
  var id = utils.checkInt(update.update_id);
  delete update.update_id;
  this.offset = id + 1;

  if (update.message !== undefined) {
    this.processMessage(update.message);
    delete update.message;
  } else if (update.inline_query !== undefined) {
    this.processInlineQuery(update.inline_query);
    delete update.inline_query;
  } else if (update.chosen_inline_result !== undefined) {
    this.processChosenInlineResult(update.chosen_inline_result);
    delete update.chosen_inline_result;
  } else if (this.options.strict) {
    throw new Error("Unknown update type!");
  }

  if (this.options.strict && Object.keys(update).length > 0)
    throw new Error("Unknown fields in update");
};

// FIXME: reexport everything else (review manually)
Bot.NetworkError = NetworkError;
Bot.TelegramError = TelegramError;
module.exports = Bot;
<commit_msg>Errors when getting updates are not fatal<commit_after>// Entry point for the library. Implements the bot object,
// get / set, lifecycle management, API calling and the
// `getUpdates` loop.
var util = require("util");
var utils = require("./utils");
var stream = require("stream");
var EventEmitter = require("events");
var FormData = require("form-data");

var model = require("./model");
var misc = require("./misc");
var message = require("./message");
var inline = require("./inline");
var reply = require("./reply");

// TODO
function NetworkError(err) { this.err = err; }
function TelegramError(err) { this.err = err; }

function Bot(authToken, options) {
  if (!(this instanceof Bot)) return new Bot(authToken, options);
  EventEmitter.call(this);

  if (options === undefined) options = {};
  if (!options.hasOwnProperty("timeout")) options.timeout = 10 * 60;
  if (!options.hasOwnProperty("strict")) options.strict = true; //FIXME: use, set to false
  if (!options.hasOwnProperty("actionInterval")) options.actionInterval = 3000;
  if (!options.hasOwnProperty("autodetect")) options.autodetect = true;
  if (!options.hasOwnProperty("retryInterval")) options.retryInterval = 2000;
  if (!options.hasOwnProperty("immediate")) options.immediate = false;

  if (!utils.isStr(authToken)) throw new Error("Invalid auth token specified");

  this.options = options;
  this.authToken = authToken;
  this.data = {};

  message.initMessage.call(this);
  inline.initInline.call(this);
  misc.initMisc.call(this);
  reply.initReply.call(this);

  this.on("ready", startLoop);
  process.nextTick(firstDetection.bind(this));
}

util.inherits(Bot, EventEmitter);

Bot.prototype.get = function get(name) {
  return this.data[name];
};

Bot.prototype.set = function set(name, value) {
  this.data[name] = value;
};

// FIXME: wrap errors of form, req and res in NetworkError
Bot.prototype.callMethod = function (method, parameters, cb) {
  if (cb === undefined) cb = parameters, parameters = {};

  // Prepare the body
  var form = new FormData(), setPlaceholder = true;
  form.on("error", handleResponse);
  Object.keys(parameters).forEach(function (key) {
    var value = parameters[key];
    if (value === null || value === undefined) return;
    setPlaceholder = false;

    if (utils.isNum(value) || utils.isBool(value) || utils.isStr(value))
      return form.append(key, value.toString());
    if (utils.isBuffer(value) || utils.isReadable(value))
      return form.append(key, value, value.options);
    form.append(key, JSON.stringify(value));
  });
  if (setPlaceholder) form.append("placeholder", "placeholder");

  // Make the request
  form.submit({
    agent: this.options.agent,
    protocol: "https:",
    host: "api.telegram.org",
    path: "/bot" + this.authToken + "/" + method,
    headers: { accept: "application/json" }
  }, handleResponse);

  // Parse the response
  function handleResponse(err, res) {
    if (err) return cb(new NetworkError(err));

    if (res.headers["content-type"] !== "application/json") {
      res.resume();
      return cb(new NetworkError("API response not in JSON"));
    }

    // FIXME: not the most efficient way to collect output
    var body = Buffer(0);
    res.on("error", handleResponse);
    res.on("readable", function () {
      var chunk = res.read();
      if (chunk) body = Buffer.concat([body, chunk]);
    });
    res.on("end", function () { handleBody(body, res.statusCode); });
  }

  function handleBody(body, status) {
    try {
      body = JSON.parse(body);
    } catch (err) { return cb(err); }
    if (!utils.isBool(body.ok))
      return cb(new NetworkError("ok field not a boolean in API response"));
    if (body.ok !== (status === 200))
      return cb(new NetworkError("ok field not matching JSON response"));
    if (body.ok) return cb(null, body.result);
    cb(new TelegramError(body));
  }
};

Bot.prototype.autodetect = function autodetect(cb) {
  if (!cb) cb = function () {};
  this.callMethod("getMe", function (err, result) {
    if (err) return cb(err);

    this.set("id", result.id);
    this.set("username", result.username);
    this.set("firstname", result.first_name);
    this.set("lastname", result.last_name);
    cb();
  }.bind(this));
};

function firstDetection() {
  if (this.stopped) return;

  if (this.options.autodetect)
    return this.autodetect(function (err) {
      if (err) {
        this.emit("error", err);
        return setTimeout(firstDetection.bind(this), this.options.retryInterval);
      }
      this.emit("ready");
    }.bind(this));

  if (!utils.isInt(this.get("id"))) {
    console.error("Bot was not initialized!");
    process.exit(1);
  }
  this.emit("ready");
}

function startLoop() {
  if (this.stopped) return;

  this.callMethod("getUpdates", {
    offset: this.offset,
    timeout: this.firstCycleDone ? this.options.timeout : 0,
  }, function (err, result) {
    if (err) {
      this.emit("updateError", err);
      return setTimeout(startLoop.bind(this), this.options.retryInterval);
    }

    result.forEach(this.processUpdate.bind(this));

    if (!this.firstCycleDone && result.length == 0) {
      this.firstCycleDone = true;
      this.emit("synced");
    }
    startLoop.call(this);
  }.bind(this));
}

Bot.prototype.ready = function ready(cb) {
  if (utils.isInt(this.get("id")))
    return cb();
  this.on("ready", cb);
};

Bot.prototype.synced = function synced(cb) {
  if (this.firstCycleDone)
    return cb();
  this.on("synced", cb);
};

Bot.prototype.stop = function stop() {
  if (this.stopped) return;
  this.stopped = true;
  this.emit("stopped");
};

Bot.prototype.processUpdate = function processUpdate(update) {
  var id = utils.checkInt(update.update_id);
  delete update.update_id;
  this.offset = id + 1;

  if (update.message !== undefined) {
    this.processMessage(update.message);
    delete update.message;
  } else if (update.inline_query !== undefined) {
    this.processInlineQuery(update.inline_query);
    delete update.inline_query;
  } else if (update.chosen_inline_result !== undefined) {
    this.processChosenInlineResult(update.chosen_inline_result);
    delete update.chosen_inline_result;
  } else if (this.options.strict) {
    throw new Error("Unknown update type!");
  }

  if (this.options.strict && Object.keys(update).length > 0)
    throw new Error("Unknown fields in update");
};

// FIXME: reexport everything else (review manually)
Bot.NetworkError = NetworkError;
Bot.TelegramError = TelegramError;
module.exports = Bot;
<|endoftext|>


<commit_before>import _ from 'lodash';
import irc from 'irc';
import logger from 'winston';
import Slack from 'slack-client';
import { ConfigurationError } from './errors';
import emojis from '../assets/emoji.json';
import { validateChannelMapping } from './validators';
import { highlightUsername } from './helpers';

const ALLOWED_SUBTYPES = ['me_message'];
const REQUIRED_FIELDS = ['server', 'nickname', 'slackUser', 'channelMapping', 'token'];

/**
 * An IRC bot, works as a middleman for all communication
 * @param {object} options
 */
class Bot {
  constructor(options) {
    REQUIRED_FIELDS.forEach(field => {
      if (!options[field]) {
        throw new ConfigurationError('Missing configuration field ' + field);
      }
    });

    validateChannelMapping(options.channelMapping);

    this.slack = new Slack(options.token);

    this.server = options.server;
    this.nickname = options.nickname;
    this.slackUser = options.slackUser;
    this.ircOptions = options.ircOptions;
    this.ircStatusNotices = options.ircStatusNotices || {};
    this.channels = _.values(options.channelMapping);
    this.muteSlackbot = options.muteSlackbot || false;

    this.rememberRecipientsFor = +(options.rememberRecipientsFor || 1000 * 60 * 10);
    this.lastPM = {
      user: '',
      timestamp: 0
    };

    this.channelMapping = {};

    // Remove channel passwords from the mapping and lowercase IRC channel names
    _.forOwn(options.channelMapping, (ircChan, slackChan) => {
      this.channelMapping[slackChan] = ircChan.split(' ')[0].toLowerCase();
    }, this);

    this.invertedMapping = _.invert(this.channelMapping);
    this.autoSendCommands = options.autoSendCommands || [];
  }

  connect() {
    logger.debug('Connecting to IRC and Slack');
    this.slack.login();

    const ircOptions = {
      userName: this.nickname,
      realName: this.nickname,
      channels: this.channels,
      floodProtection: true,
      floodProtectionDelay: 500,
      ...this.ircOptions
    };

    this.ircClient = new irc.Client(this.server, this.nickname, ircOptions);
    this.attachListeners();
  }

  attachListeners() {
    this.slack.on('open', () => {
      logger.debug('Connected to Slack');
    });

    this.ircClient.on('registered', message => {
      logger.debug('Registered event: ', message);
      this.autoSendCommands.forEach(element => {
        this.ircClient.send(...element);
      });
    });

    this.ircClient.on('error', error => {
      logger.error('Received error event from IRC', error);
    });

    this.slack.on('error', error => {
      logger.error('Received error event from Slack', error);
    });

    this.slack.on('presenceChange', (user, presence) => {
      if (user.name === this.slackUser) {
        if (presence === 'active') {
          this.ircClient.send('AWAY');
        } else {
          this.ircClient.send('AWAY', ' ');
        }
      }
    });

    this.slack.on('message', message => {
      // Ignore bot messages and people leaving/joining
      if (message.type === 'message' &&
        (!message.subtype || ALLOWED_SUBTYPES.indexOf(message.subtype) > -1)) {
        this.sendToIRC(message);
      }
    });

    this.ircClient.on('message', this.sendToSlack.bind(this));

    this.ircClient.on('notice', (author, to, text) => {
      const formattedText = '*' + text + '*';
      this.sendToSlack(author, to, formattedText);
    });

    this.ircClient.on('action', (author, to, text) => {
      const formattedText = '_' + text + '_';
      this.sendToSlack(author, to, formattedText);
    });

    this.ircClient.on('topic', (channel, topic, nick) => {
      this.sendToSlack(false, channel, `*${nick}* has changed the topic to: *${topic}*`);
    });

    this.ircClient.on('kick', (channel, nick, by, reason) => {
      this.sendToSlack(false, channel, `*${by}* has kicked *${nick}* (_${reason}_)`);
    });

    this.ircClient.on('kill', (nick, reason, channels) => {
      channels.forEach(channel => {
        this.sendToSlack(false, channel, `*${nick}* has been killed (_${reason}_)`);
      });
    });

    this.ircClient.on('invite', (channel, from) => {
      logger.debug('Received invite:', channel, from);
      if (!this.invertedMapping[channel]) {
        logger.debug('Channel not found in config, not joining:', channel);
      } else {
        this.ircClient.join(channel);
        logger.debug('Joining channel:', channel);
      }
    });

    if (this.ircStatusNotices.join) {
      this.ircClient.on('join', (channel, nick) => {
        if (nick !== this.nickname) {
          this.sendToSlack(false, channel, `*${nick}* has joined`);
        }
      });
    }

    if (this.ircStatusNotices.leave) {
      this.ircClient.on('part', (channel, nick, reason) => {
        this.sendToSlack(false, channel, `*${nick}* has left (_${reason}_)`);
      });

      this.ircClient.on('quit', (nick, reason, channels) => {
        channels.forEach(channel => {
          this.sendToSlack(false, channel, `*${nick}* has quit (_${reason}_)`);
        });
      });
    }

    if (this.ircStatusNotices.changeNick) {
      this.ircClient.on('nick', (oldNick, newNick, channels) => {
        channels.forEach(channel => {
          this.sendToSlack(false, channel, `*${oldNick}* is now known as *${newNick}*`);
        });
      });
    }

    if (this.ircStatusNotices.modes) {
      this.ircClient.on('+mode', (channel, by, mode, argument) => {
        this.sendToSlack(false, channel, `*${by}* sets mode *+${mode}* on _${argument || channel}_`);
      });
      this.ircClient.on('-mode', (channel, by, mode, argument) => {
        this.sendToSlack(false, channel, `*${by}* sets mode *-${mode}* on _${argument || channel}_`);
      });
    }
  }

  parseText(text) {
    return text
      .replace(/\n|\r\n|\r/g, ' ')
      .replace(/&/g, '&')
      .replace(/</g, '<')
      .replace(/>/g, '>')
      .replace(/<!channel>/g, '@channel')
      .replace(/<!group>/g, '@group')
      .replace(/<!everyone>/g, '@everyone')
      .replace(/<#(C\w+)\|?(\w+)?>/g, (match, channelId, readable) => {
        const { name } = this.slack.getChannelByID(channelId);
        return readable || `#${name}`;
      })
      .replace(/<@(U\w+)\|?(\w+)?>/g, (match, userId, readable) => {
        const { name } = this.slack.getUserByID(userId);
        return readable || `@${name}`;
      })
      .replace(/<(?!!)(\S+)>/g, (match, link) => link)
      .replace(/<!(\w+)\|?(\w+)?>/g, (match, command, label) =>
        `<${label || command}>`
      )
      .replace(/\:(\w+)\:/g, (match, emoji) => {
        if (emoji in emojis) {
          return emojis[emoji];
        }

        return match;
      });
  }

  sendDMToIRC(message, channel) {
    const text = this.parseText(message.getBody());
    const cmdMatch = (/^[A-Z]+\s\S/).test(text);
    const pmMatch = (/^(\S+):\s+(.+)/i).exec(text);

    if (cmdMatch) {
      logger.debug('Sending raw command to IRC', text);
      this.ircClient.send(...text.split(' '));
      channel.send(`_sent raw command_`);
      return;
    }

    if (pmMatch) {
      const [, user, msg] = pmMatch;
      logger.debug('Sending /msg to IRC user', user, msg);
      this.ircClient.send('PRIVMSG', user, msg);
      this.lastPM.user = user;
      this.lastPM.timestamp = Date.now();
      return;
    }

    const lastMessage = Date.now() - this.lastPM.timestamp;
    if (lastMessage < this.rememberRecipientsFor) {
      const user = this.lastPM.user;
      logger.debug('Sending /msg to last messaged IRC user', user, text);
      this.ircClient.send('PRIVMSG', user, text);
      this.lastPM.user = user;
      this.lastPM.timestamp = Date.now();
      return;
    }

    if (this.lastPM.user) {
      logger.debug('Not sending message', text, 'since user', this.lastPM.user, 'was messaged',
          lastMessage, 'ago, which is more than', this.rememberRecipientsFor);
      channel.send(`_it's been too long since your last message, please specify the user_`);
      return;
    }

    logger.debug('Not sending message', text, 'since no users have been messaged');
    channel.send(`_you haven't messaged anyone yet, please specify the user_`);
  }

  sendToIRC(message) {
    const channel = this.slack.getChannelGroupOrDMByID(message.channel);
    if (!channel) {
      logger.info('Received message from a channel the bot isn\'t in:',
        message.channel);
      return;
    }

    if (this.muteSlackbot && message.user === 'USLACKBOT') {
      logger.debug(`Muted message from Slackbot: "${message.text}"`);
      return;
    }

    if (channel.is_im) {
      this.sendDMToIRC(message, channel);
      return;
    }

    const channelName = channel.is_channel ? `#${channel.name}` : channel.name;
    const ircChannel = this.channelMapping[channelName];

    logger.debug('Channel Mapping', channelName, this.channelMapping[channelName]);
    if (ircChannel) {
      let text = this.parseText(message.getBody());

      if (message.subtype === 'me_message') {
        text = `\x01ACTION ${text}\x01`;
      }
      logger.debug('Sending message to IRC', channelName, text);
      this.ircClient.say(ircChannel, text);
    }
  }

  sendToSlack(author, channel, text) {
    const slackChannelName = channel === this.nickname ?
        this.slackUser :
        this.invertedMapping[channel.toLowerCase()];
    if (slackChannelName) {
      const slackChannel = this.slack.getChannelGroupOrDMByName(slackChannelName);

      // If it's a private group and the bot isn't in it, we won't find anything here.
      // If it's a channel however, we need to check is_member.
      if (!slackChannel ||
          (!slackChannel.is_member && !slackChannel.is_group && !slackChannel.is_im)) {
        logger.info('Tried to send a message to a channel the bot isn\'t in:', slackChannelName);
        return;
      }

      const currentChannelUsernames = (slackChannel.members || []).map(member =>
        this.slack.getUserByID(member).name
      );

      const mappedText = currentChannelUsernames.reduce((current, username) =>
        highlightUsername(username, current)
      , text);

      logger.debug('Sending message to Slack', mappedText, channel, '->', slackChannelName);
      if (author) {
        slackChannel.postMessage({
          text: mappedText,
          username: author,
          parse: 'full',
          icon_url: `http://api.adorable.io/avatars/48/${author}.png`
        });
      } else {
        slackChannel.send(mappedText);
      }
    }
  }
}

export default Bot;
<commit_msg>optionally send whois to Slack<commit_after>import _ from 'lodash';
import irc from 'irc';
import logger from 'winston';
import Slack from 'slack-client';
import { ConfigurationError } from './errors';
import emojis from '../assets/emoji.json';
import { validateChannelMapping } from './validators';
import { highlightUsername } from './helpers';

const ALLOWED_SUBTYPES = ['me_message'];
const REQUIRED_FIELDS = ['server', 'nickname', 'slackUser', 'channelMapping', 'token'];

/**
 * An IRC bot, works as a middleman for all communication
 * @param {object} options
 */
class Bot {
  constructor(options) {
    REQUIRED_FIELDS.forEach(field => {
      if (!options[field]) {
        throw new ConfigurationError('Missing configuration field ' + field);
      }
    });

    validateChannelMapping(options.channelMapping);

    this.slack = new Slack(options.token);

    this.server = options.server;
    this.nickname = options.nickname;
    this.slackUser = options.slackUser;
    this.ircOptions = options.ircOptions;
    this.ircStatusNotices = options.ircStatusNotices || {};
    this.channels = _.values(options.channelMapping);
    this.muteSlackbot = options.muteSlackbot || false;

    this.rememberRecipientsFor = +(options.rememberRecipientsFor || 1000 * 60 * 10);
    this.lastPM = {
      user: '',
      timestamp: 0
    };

    this.channelMapping = {};

    // Remove channel passwords from the mapping and lowercase IRC channel names
    _.forOwn(options.channelMapping, (ircChan, slackChan) => {
      this.channelMapping[slackChan] = ircChan.split(' ')[0].toLowerCase();
    }, this);

    this.invertedMapping = _.invert(this.channelMapping);
    this.autoSendCommands = options.autoSendCommands || [];
  }

  connect() {
    logger.debug('Connecting to IRC and Slack');
    this.slack.login();

    const ircOptions = {
      userName: this.nickname,
      realName: this.nickname,
      channels: this.channels,
      floodProtection: true,
      floodProtectionDelay: 500,
      ...this.ircOptions
    };

    this.ircClient = new irc.Client(this.server, this.nickname, ircOptions);
    this.attachListeners();
  }

  attachListeners() {
    this.slack.on('open', () => {
      logger.debug('Connected to Slack');
    });

    this.ircClient.on('registered', message => {
      logger.debug('Registered event: ', message);
      this.autoSendCommands.forEach(element => {
        this.ircClient.send(...element);
      });
    });

    this.ircClient.on('error', error => {
      logger.error('Received error event from IRC', error);
    });

    this.slack.on('error', error => {
      logger.error('Received error event from Slack', error);
    });

    this.slack.on('presenceChange', (user, presence) => {
      if (user.name === this.slackUser) {
        if (presence === 'active') {
          this.ircClient.send('AWAY');
        } else {
          this.ircClient.send('AWAY', ' ');
        }
      }
    });

    this.slack.on('message', message => {
      // Ignore bot messages and people leaving/joining
      if (message.type === 'message' &&
        (!message.subtype || ALLOWED_SUBTYPES.indexOf(message.subtype) > -1)) {
        this.sendToIRC(message);
      }
    });

    this.ircClient.on('message', this.sendToSlack.bind(this));

    this.ircClient.on('notice', (author, to, text) => {
      const formattedText = '*' + text + '*';
      this.sendToSlack(author, to, formattedText);
    });

    this.ircClient.on('action', (author, to, text) => {
      const formattedText = '_' + text + '_';
      this.sendToSlack(author, to, formattedText);
    });

    this.ircClient.on('topic', (channel, topic, nick) => {
      this.sendToSlack(false, channel, `*${nick}* has changed the topic to: *${topic}*`);
    });

    this.ircClient.on('kick', (channel, nick, by, reason) => {
      this.sendToSlack(false, channel, `*${by}* has kicked *${nick}* (_${reason}_)`);
    });

    this.ircClient.on('kill', (nick, reason, channels) => {
      channels.forEach(channel => {
        this.sendToSlack(false, channel, `*${nick}* has been killed (_${reason}_)`);
      });
    });

    this.ircClient.on('invite', (channel, from) => {
      logger.debug('Received invite:', channel, from);
      if (!this.invertedMapping[channel]) {
        logger.debug('Channel not found in config, not joining:', channel);
      } else {
        this.ircClient.join(channel);
        logger.debug('Joining channel:', channel);
      }
    });

    this.ircClient.on('whois', (info) => {
      const date = new Date(Date.now() - (info.idle || 0) * 1000);
      this.sendToSlack(false, this.nickname, `
        WHOIS for *${info.nick}*
        (_${info.user}@${info.host}_): _${info.realname}_
        _${info.server}_ :_${info.serverinfo}_
        ${info.accountinfo} _${info.account}_
        idle since _${date.toDateString()}, ${date.toLocaleTimeString()}_
      `);
    });

    if (this.ircStatusNotices.join) {
      this.ircClient.on('join', (channel, nick) => {
        if (nick !== this.nickname) {
          this.sendToSlack(false, channel, `*${nick}* has joined`);
        }
      });
    }

    if (this.ircStatusNotices.leave) {
      this.ircClient.on('part', (channel, nick, reason) => {
        this.sendToSlack(false, channel, `*${nick}* has left (_${reason}_)`);
      });

      this.ircClient.on('quit', (nick, reason, channels) => {
        channels.forEach(channel => {
          this.sendToSlack(false, channel, `*${nick}* has quit (_${reason}_)`);
        });
      });
    }

    if (this.ircStatusNotices.changeNick) {
      this.ircClient.on('nick', (oldNick, newNick, channels) => {
        channels.forEach(channel => {
          this.sendToSlack(false, channel, `*${oldNick}* is now known as *${newNick}*`);
        });
      });
    }

    if (this.ircStatusNotices.modes) {
      this.ircClient.on('+mode', (channel, by, mode, argument) => {
        this.sendToSlack(false, channel, `*${by}* sets mode *+${mode}* on _${argument || channel}_`);
      });
      this.ircClient.on('-mode', (channel, by, mode, argument) => {
        this.sendToSlack(false, channel, `*${by}* sets mode *-${mode}* on _${argument || channel}_`);
      });
    }
  }

  parseText(text) {
    return text
      .replace(/\n|\r\n|\r/g, ' ')
      .replace(/&/g, '&')
      .replace(/</g, '<')
      .replace(/>/g, '>')
      .replace(/<!channel>/g, '@channel')
      .replace(/<!group>/g, '@group')
      .replace(/<!everyone>/g, '@everyone')
      .replace(/<#(C\w+)\|?(\w+)?>/g, (match, channelId, readable) => {
        const { name } = this.slack.getChannelByID(channelId);
        return readable || `#${name}`;
      })
      .replace(/<@(U\w+)\|?(\w+)?>/g, (match, userId, readable) => {
        const { name } = this.slack.getUserByID(userId);
        return readable || `@${name}`;
      })
      .replace(/<(?!!)(\S+)>/g, (match, link) => link)
      .replace(/<!(\w+)\|?(\w+)?>/g, (match, command, label) =>
        `<${label || command}>`
      )
      .replace(/\:(\w+)\:/g, (match, emoji) => {
        if (emoji in emojis) {
          return emojis[emoji];
        }

        return match;
      });
  }

  sendDMToIRC(message, channel) {
    const text = this.parseText(message.getBody());
    const cmdMatch = (/^[A-Z]+\s\S/).test(text);
    const pmMatch = (/^(\S+):\s+(.+)/i).exec(text);

    if (cmdMatch) {
      logger.debug('Sending raw command to IRC', text);
      this.ircClient.send(...text.split(' '));
      channel.send(`_sent raw command_`);
      return;
    }

    if (pmMatch) {
      const [, user, msg] = pmMatch;
      logger.debug('Sending /msg to IRC user', user, msg);
      this.ircClient.send('PRIVMSG', user, msg);
      this.lastPM.user = user;
      this.lastPM.timestamp = Date.now();
      return;
    }

    const lastMessage = Date.now() - this.lastPM.timestamp;
    if (lastMessage < this.rememberRecipientsFor) {
      const user = this.lastPM.user;
      logger.debug('Sending /msg to last messaged IRC user', user, text);
      this.ircClient.send('PRIVMSG', user, text);
      this.lastPM.user = user;
      this.lastPM.timestamp = Date.now();
      return;
    }

    if (this.lastPM.user) {
      logger.debug('Not sending message', text, 'since user', this.lastPM.user, 'was messaged',
          lastMessage, 'ago, which is more than', this.rememberRecipientsFor);
      channel.send(`_it's been too long since your last message, please specify the user_`);
      return;
    }

    logger.debug('Not sending message', text, 'since no users have been messaged');
    channel.send(`_you haven't messaged anyone yet, please specify the user_`);
  }

  sendToIRC(message) {
    const channel = this.slack.getChannelGroupOrDMByID(message.channel);
    if (!channel) {
      logger.info('Received message from a channel the bot isn\'t in:',
        message.channel);
      return;
    }

    if (this.muteSlackbot && message.user === 'USLACKBOT') {
      logger.debug(`Muted message from Slackbot: "${message.text}"`);
      return;
    }

    if (channel.is_im) {
      this.sendDMToIRC(message, channel);
      return;
    }

    const channelName = channel.is_channel ? `#${channel.name}` : channel.name;
    const ircChannel = this.channelMapping[channelName];

    logger.debug('Channel Mapping', channelName, this.channelMapping[channelName]);
    if (ircChannel) {
      let text = this.parseText(message.getBody());

      if (message.subtype === 'me_message') {
        text = `\x01ACTION ${text}\x01`;
      }
      logger.debug('Sending message to IRC', channelName, text);
      this.ircClient.say(ircChannel, text);
    }
  }

  sendToSlack(author, channel, text) {
    const slackChannelName = channel === this.nickname ?
        this.slackUser :
        this.invertedMapping[channel.toLowerCase()];
    if (slackChannelName) {
      const slackChannel = this.slack.getChannelGroupOrDMByName(slackChannelName);

      // If it's a private group and the bot isn't in it, we won't find anything here.
      // If it's a channel however, we need to check is_member.
      if (!slackChannel ||
          (!slackChannel.is_member && !slackChannel.is_group && !slackChannel.is_im)) {
        logger.info('Tried to send a message to a channel the bot isn\'t in:', slackChannelName);
        return;
      }

      const currentChannelUsernames = (slackChannel.members || []).map(member =>
        this.slack.getUserByID(member).name
      );

      const mappedText = currentChannelUsernames.reduce((current, username) =>
        highlightUsername(username, current)
      , text);

      logger.debug('Sending message to Slack', mappedText, channel, '->', slackChannelName);
      if (author) {
        slackChannel.postMessage({
          text: mappedText,
          username: author,
          parse: 'full',
          icon_url: `http://api.adorable.io/avatars/48/${author}.png`
        });
      } else {
        slackChannel.send(mappedText);
      }
    }
  }
}

export default Bot;
<|endoftext|>


<commit_before>import Ember from 'ember-metal/core'; // Ember.assert
import emberKeys from "ember-metal/keys";
import dictionary from 'ember-metal/dictionary';
import Registry from 'container/registry';

/*
 TODO: Questions to resolve due to Registry / Container split:

 * When registry.unregister is called, is it important for containers to clear their caches of that item?
 * Do we need the restriction on re-registering a factory for a key that's already been looked up?
 */

// A lightweight container that helps to assemble and decouple components.
// Public api for the container is still in flux.
// The public api, specified on the application namespace should be considered the stable api.
function Container(options) {
  options = options || {};

  this.registry       = options.registry || new Registry();
  this.cache          = dictionary(options.cache || null);
  this.factoryCache   = dictionary(options.factoryCache || null);

  if (Ember.FEATURES.isEnabled('ember-metal-injected-properties')) {
    this.validationCache = dictionary(options.validationCache || null);
  }
}

Container.prototype = {
  /**
   @property registry
   @type Registry
   */
  registry: null,

  /**
   @property cache
   @type InheritingDict
   */
  cache: null,

  /**
   @property factoryCache
   @type InheritingDict
   */
  factoryCache: null,

  /**
   @property validationCache
   @type InheritingDict
   */
  validationCache: null,

  /**
   Given a fullName return a corresponding instance.

   The default behaviour is for lookup to return a singleton instance.
   The singleton is scoped to the container, allowing multiple containers
   to all have their own locally scoped singletons.

   ```javascript
   var registry = new Registry();
   var container = new Container({registry: registry});

   registry.register('api:twitter', Twitter);

   var twitter = container.lookup('api:twitter');

   twitter instanceof Twitter; // => true

   // by default the container will return singletons
   var twitter2 = container.lookup('api:twitter');
   twitter2 instanceof Twitter; // => true

   twitter === twitter2; //=> true
   ```

   If singletons are not wanted an optional flag can be provided at lookup.

   ```javascript
   var registry = new Registry();
   var container = new Container({registry: registry});

   registry.register('api:twitter', Twitter);

   var twitter = container.lookup('api:twitter', { singleton: false });
   var twitter2 = container.lookup('api:twitter', { singleton: false });

   twitter === twitter2; //=> false
   ```

   @method lookup
   @param {String} fullName
   @param {Object} options
   @return {any}
   */
  lookup: function(fullName, options) {
    Ember.assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
    return lookup(this, this.registry.normalize(fullName), options);
  },

  /**
   Given a fullName return the corresponding factory.

   @method lookupFactory
   @param {String} fullName
   @return {any}
   */
  lookupFactory: function(fullName) {
    Ember.assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
    return factoryFor(this, this.registry.normalize(fullName));
  },

  /**
   A depth first traversal, destroying the container, its descendant containers and all
   their managed objects.

   @method destroy
   */
  destroy: function() {
    eachDestroyable(this, function(item) {
      if (item.destroy) {
        item.destroy();
      }
    });

    this.isDestroyed = true;
  },

  /**
   @method reset
   */
  reset: function(fullName) {
    if (fullName) {
      resetMember(this, this.registry.normalize(fullName));
    } else {
      resetCache(this);
    }
  }
};

(function exposeRegistryMethods() {
  var methods = [
    'register',
    'unregister',
    'resolve',
    'normalize',
    'typeInjection',
    'injection',
    'factoryInjection',
    'factoryTypeInjection',
    'has',
    'options',
    'optionsForType'
  ];

  function exposeRegistryMethod(method) {
    Container.prototype[method] = function() {
      Ember.deprecate(method + ' should be called on the registry instead of the container');
      return this.registry[method].apply(this.registry, arguments);
    };
  }

  for (var i = 0, l = methods.length; i < l; i++) {
    exposeRegistryMethod(methods[i]);
  }
})();

function lookup(container, fullName, options) {
  options = options || {};

  if (container.cache[fullName] && options.singleton !== false) {
    return container.cache[fullName];
  }

  var value = instantiate(container, fullName);

  if (value === undefined) { return; }

  if (container.registry.getOption(fullName, 'singleton') !== false && options.singleton !== false) {
    container.cache[fullName] = value;
  }

  return value;
}

function buildInjections(container, injections) {
  var hash = {};

  if (!injections) { return hash; }

  container.registry.validateInjections(injections);

  var injection;

  for (var i = 0, length = injections.length; i < length; i++) {
    injection = injections[i];
    hash[injection.property] = lookup(container, injection.fullName);
  }

  return hash;
}

function factoryFor(container, fullName) {
  var cache = container.factoryCache;
  if (cache[fullName]) {
    return cache[fullName];
  }
  var registry = container.registry;
  var factory = registry.resolve(fullName);
  if (factory === undefined) { return; }

  var type = fullName.split(':')[0];
  if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) {
    if (factory && typeof factory._onLookup === 'function') {
      factory._onLookup(fullName);
    }

    // TODO: think about a 'safe' merge style extension
    // for now just fallback to create time injection
    cache[fullName] = factory;
    return factory;

  } else {
    var injections = injectionsFor(container, fullName);
    var factoryInjections = factoryInjectionsFor(container, fullName);

    factoryInjections._toString = registry.makeToString(factory, fullName);

    var injectedFactory = factory.extend(injections);
    injectedFactory.reopenClass(factoryInjections);

    if (factory && typeof factory._onLookup === 'function') {
      factory._onLookup(fullName);
    }

    cache[fullName] = injectedFactory;

    return injectedFactory;
  }
}

function injectionsFor(container, fullName) {
  var registry = container.registry;
  var splitName = fullName.split(':');
  var type = splitName[0];
  var injections = [];

  injections = injections.concat(registry.typeInjections[type] || []);
  injections = injections.concat(registry.injections[fullName] || []);

  injections = buildInjections(container, injections);
  injections._debugContainerKey = fullName;
  injections.container = container;

  return injections;
}

function factoryInjectionsFor(container, fullName) {
  var registry = container.registry;
  var splitName = fullName.split(':');
  var type = splitName[0];
  var factoryInjections = [];

  factoryInjections = factoryInjections.concat(registry.factoryTypeInjections[type] || []);
  factoryInjections = factoryInjections.concat(registry.factoryInjections[fullName] || []);

  factoryInjections = buildInjections(container, factoryInjections);
  factoryInjections._debugContainerKey = fullName;

  return factoryInjections;
}

function instantiate(container, fullName) {
  var factory = factoryFor(container, fullName);
  var lazyInjections, validationCache;

  if (container.registry.getOption(fullName, 'instantiate') === false) {
    return factory;
  }

  if (factory) {
    if (typeof factory.create !== 'function') {
      throw new Error('Failed to create an instance of \'' + fullName + '\'. ' +
      'Most likely an improperly defined class or an invalid module export.');
    }

    if (Ember.FEATURES.isEnabled('ember-metal-injected-properties')) {
      validationCache = container.validationCache;

      // Ensure that all lazy injections are valid at instantiation time
      if (!validationCache[fullName] && typeof factory._lazyInjections === 'function') {
        lazyInjections = factory._lazyInjections();
        lazyInjections = container.registry.normalizeInjectionsHash(lazyInjections);

        container.registry.validateInjections(lazyInjections);
      }

      validationCache[fullName] = true;
    }

    if (typeof factory.extend === 'function') {
      // assume the factory was extendable and is already injected
      return factory.create();
    } else {
      // assume the factory was extendable
      // to create time injections
      // TODO: support new'ing for instantiation and merge injections for pure JS Functions
      return factory.create(injectionsFor(container, fullName));
    }
  }
}

function eachDestroyable(container, callback) {
  var cache = container.cache;
  var keys = emberKeys(cache);
  var key, value;

  for (var i = 0, l = keys.length; i < l; i++) {
    key = keys[i];
    value = cache[key];

    if (container.registry.getOption(key, 'instantiate') !== false) {
      callback(value);
    }
  }
}

function resetCache(container) {
  eachDestroyable(container, function(value) {
    if (value.destroy) {
      value.destroy();
    }
  });

  container.cache.dict = dictionary(null);
}

function resetMember(container, fullName) {
  var member = container.cache[fullName];
  if (member) {
    if (member.destroy) {
      member.destroy();
    }
    delete container.cache[fullName];
  }
  delete container.factoryCache[fullName];
}

export default Container;
<commit_msg>Require a Registry instance to be passed into Container constructor<commit_after>import Ember from 'ember-metal/core'; // Ember.assert
import emberKeys from "ember-metal/keys";
import dictionary from 'ember-metal/dictionary';

/*
 TODO: Questions to resolve due to Registry / Container split:

 * When registry.unregister is called, is it important for containers to clear their caches of that item?
 * Do we need the restriction on re-registering a factory for a key that's already been looked up?
 */

// A lightweight container that helps to assemble and decouple components.
// Public api for the container is still in flux.
// The public api, specified on the application namespace should be considered the stable api.
function Container(options) {
  Ember.assert("A Registry instance must be passed as an option when constructing a Container.", options && options.registry);

  this.registry       = options.registry;
  this.cache          = dictionary(options.cache || null);
  this.factoryCache   = dictionary(options.factoryCache || null);

  if (Ember.FEATURES.isEnabled('ember-metal-injected-properties')) {
    this.validationCache = dictionary(options.validationCache || null);
  }
}

Container.prototype = {
  /**
   @property registry
   @type Registry
   */
  registry: null,

  /**
   @property cache
   @type InheritingDict
   */
  cache: null,

  /**
   @property factoryCache
   @type InheritingDict
   */
  factoryCache: null,

  /**
   @property validationCache
   @type InheritingDict
   */
  validationCache: null,

  /**
   Given a fullName return a corresponding instance.

   The default behaviour is for lookup to return a singleton instance.
   The singleton is scoped to the container, allowing multiple containers
   to all have their own locally scoped singletons.

   ```javascript
   var registry = new Registry();
   var container = new Container({registry: registry});

   registry.register('api:twitter', Twitter);

   var twitter = container.lookup('api:twitter');

   twitter instanceof Twitter; // => true

   // by default the container will return singletons
   var twitter2 = container.lookup('api:twitter');
   twitter2 instanceof Twitter; // => true

   twitter === twitter2; //=> true
   ```

   If singletons are not wanted an optional flag can be provided at lookup.

   ```javascript
   var registry = new Registry();
   var container = new Container({registry: registry});

   registry.register('api:twitter', Twitter);

   var twitter = container.lookup('api:twitter', { singleton: false });
   var twitter2 = container.lookup('api:twitter', { singleton: false });

   twitter === twitter2; //=> false
   ```

   @method lookup
   @param {String} fullName
   @param {Object} options
   @return {any}
   */
  lookup: function(fullName, options) {
    Ember.assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
    return lookup(this, this.registry.normalize(fullName), options);
  },

  /**
   Given a fullName return the corresponding factory.

   @method lookupFactory
   @param {String} fullName
   @return {any}
   */
  lookupFactory: function(fullName) {
    Ember.assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
    return factoryFor(this, this.registry.normalize(fullName));
  },

  /**
   A depth first traversal, destroying the container, its descendant containers and all
   their managed objects.

   @method destroy
   */
  destroy: function() {
    eachDestroyable(this, function(item) {
      if (item.destroy) {
        item.destroy();
      }
    });

    this.isDestroyed = true;
  },

  /**
   @method reset
   */
  reset: function(fullName) {
    if (fullName) {
      resetMember(this, this.registry.normalize(fullName));
    } else {
      resetCache(this);
    }
  }
};

(function exposeRegistryMethods() {
  var methods = [
    'register',
    'unregister',
    'resolve',
    'normalize',
    'typeInjection',
    'injection',
    'factoryInjection',
    'factoryTypeInjection',
    'has',
    'options',
    'optionsForType'
  ];

  function exposeRegistryMethod(method) {
    Container.prototype[method] = function() {
      Ember.deprecate(method + ' should be called on the registry instead of the container');
      return this.registry[method].apply(this.registry, arguments);
    };
  }

  for (var i = 0, l = methods.length; i < l; i++) {
    exposeRegistryMethod(methods[i]);
  }
})();

function lookup(container, fullName, options) {
  options = options || {};

  if (container.cache[fullName] && options.singleton !== false) {
    return container.cache[fullName];
  }

  var value = instantiate(container, fullName);

  if (value === undefined) { return; }

  if (container.registry.getOption(fullName, 'singleton') !== false && options.singleton !== false) {
    container.cache[fullName] = value;
  }

  return value;
}

function buildInjections(container, injections) {
  var hash = {};

  if (!injections) { return hash; }

  container.registry.validateInjections(injections);

  var injection;

  for (var i = 0, length = injections.length; i < length; i++) {
    injection = injections[i];
    hash[injection.property] = lookup(container, injection.fullName);
  }

  return hash;
}

function factoryFor(container, fullName) {
  var cache = container.factoryCache;
  if (cache[fullName]) {
    return cache[fullName];
  }
  var registry = container.registry;
  var factory = registry.resolve(fullName);
  if (factory === undefined) { return; }

  var type = fullName.split(':')[0];
  if (!factory || typeof factory.extend !== 'function' || (!Ember.MODEL_FACTORY_INJECTIONS && type === 'model')) {
    if (factory && typeof factory._onLookup === 'function') {
      factory._onLookup(fullName);
    }

    // TODO: think about a 'safe' merge style extension
    // for now just fallback to create time injection
    cache[fullName] = factory;
    return factory;

  } else {
    var injections = injectionsFor(container, fullName);
    var factoryInjections = factoryInjectionsFor(container, fullName);

    factoryInjections._toString = registry.makeToString(factory, fullName);

    var injectedFactory = factory.extend(injections);
    injectedFactory.reopenClass(factoryInjections);

    if (factory && typeof factory._onLookup === 'function') {
      factory._onLookup(fullName);
    }

    cache[fullName] = injectedFactory;

    return injectedFactory;
  }
}

function injectionsFor(container, fullName) {
  var registry = container.registry;
  var splitName = fullName.split(':');
  var type = splitName[0];
  var injections = [];

  injections = injections.concat(registry.typeInjections[type] || []);
  injections = injections.concat(registry.injections[fullName] || []);

  injections = buildInjections(container, injections);
  injections._debugContainerKey = fullName;
  injections.container = container;

  return injections;
}

function factoryInjectionsFor(container, fullName) {
  var registry = container.registry;
  var splitName = fullName.split(':');
  var type = splitName[0];
  var factoryInjections = [];

  factoryInjections = factoryInjections.concat(registry.factoryTypeInjections[type] || []);
  factoryInjections = factoryInjections.concat(registry.factoryInjections[fullName] || []);

  factoryInjections = buildInjections(container, factoryInjections);
  factoryInjections._debugContainerKey = fullName;

  return factoryInjections;
}

function instantiate(container, fullName) {
  var factory = factoryFor(container, fullName);
  var lazyInjections, validationCache;

  if (container.registry.getOption(fullName, 'instantiate') === false) {
    return factory;
  }

  if (factory) {
    if (typeof factory.create !== 'function') {
      throw new Error('Failed to create an instance of \'' + fullName + '\'. ' +
      'Most likely an improperly defined class or an invalid module export.');
    }

    if (Ember.FEATURES.isEnabled('ember-metal-injected-properties')) {
      validationCache = container.validationCache;

      // Ensure that all lazy injections are valid at instantiation time
      if (!validationCache[fullName] && typeof factory._lazyInjections === 'function') {
        lazyInjections = factory._lazyInjections();
        lazyInjections = container.registry.normalizeInjectionsHash(lazyInjections);

        container.registry.validateInjections(lazyInjections);
      }

      validationCache[fullName] = true;
    }

    if (typeof factory.extend === 'function') {
      // assume the factory was extendable and is already injected
      return factory.create();
    } else {
      // assume the factory was extendable
      // to create time injections
      // TODO: support new'ing for instantiation and merge injections for pure JS Functions
      return factory.create(injectionsFor(container, fullName));
    }
  }
}

function eachDestroyable(container, callback) {
  var cache = container.cache;
  var keys = emberKeys(cache);
  var key, value;

  for (var i = 0, l = keys.length; i < l; i++) {
    key = keys[i];
    value = cache[key];

    if (container.registry.getOption(key, 'instantiate') !== false) {
      callback(value);
    }
  }
}

function resetCache(container) {
  eachDestroyable(container, function(value) {
    if (value.destroy) {
      value.destroy();
    }
  });

  container.cache.dict = dictionary(null);
}

function resetMember(container, fullName) {
  var member = container.cache[fullName];
  if (member) {
    if (member.destroy) {
      member.destroy();
    }
    delete container.cache[fullName];
  }
  delete container.factoryCache[fullName];
}

export default Container;
<|endoftext|>