Skip to content

Instantly share code, notes, and snippets.

@koresar
Last active January 26, 2017 01:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koresar/55c6ea8c842f2a16247bd3ad24b8eb22 to your computer and use it in GitHub Desktop.
Save koresar/55c6ea8c842f2a16247bd3ad24b8eb22 to your computer and use it in GitHub Desktop.
Collision allowance and protection
const stampit = require('stampit');
const ProtectMethodCollisions = stampit({
statics: {
protectMethodCollisions(name) {
if (this.compose.methods && this.compose.methods[name]) {
const method = this.compose.methods[name];
function collisionProtectedMethodWrapper() {
return method.apply(this, arguments);
}
collisionProtectedMethodWrapper.protectCollisions = true; // core logic in this line!
return this.methods({[name]: collisionProtectedMethodWrapper});
}
return this.compose();
}
}
});
const ProtectRedrawMethodCollisions = ProtectMethodCollisions.protectMethodCollisions('redraw');
const Dog = stampit(); // whatever stamp
const DogWithProtectedRedraw = Dog.compose(ProtectRedrawMethodCollisions);
const DeferMethodCollisions = stampit({
deepConf: {
DeferMethodCollisions: []
},
statics: {
deferMethodCollisions(name) {
return this.deepConf({DeferMethodCollisions: [name]});
}
},
composers: ({stamp, composables}) => {
const methodsArray = composables.map(c => isStamp(c) ? c.compose : c).map(d => d && d.methods).filter(d => d);
stamp.compose.deepConfiguration.DeferMethodCollisions.forEach(methodName => {
const methods = methodsArray.map(metadata => metadata && metadata[methodName]).filter(m => m);
if (methods.length > 1) {
const collisionProtectedMethods = methods.filter(m => m.protectCollisions); // find protected methods
if (collisionProtectedMethods.length > 0) {
throw new Error(`Attempt to compose collision protected method "${methodName}"`)
}
stamp.compose.methods[methodName] = function () {
for (let i = 0; i < methods.length; i++) method.apply(this, arguments);
}
}
});
}
});
const DeferRedrawMethodCollisions = DeferMethodCollisions.deferMethodCollisions('redraw');
const Circle = stampit({methods: {redraw(){ console.log('Circle'); }}}).compose(DeferRedrawMethodCollisions);
const Triangle = stampit({methods: {redraw(){ console.log('Triangle'); }}}).compose(DeferRedrawMethodCollisions);
stampit()
.compose(Circle) // all good
.compose(Triangle) // all good
.compose(DogWithProtectedRedraw); // THROWS !!!
const stampit = require('stampit');
const isStamp = require('stampit/isStamp');
const _ = require('lodash');
const MethodCollisions = stampit({
statics: {
methodCollisions(protectionType, ...names) {
if (!this.compose.methods) return this.compose();
const newMethods = {};
_.forEach(names, name => {
const method = this.compose.methods[name];
if (!_.isFunction(method)) return;
// Wrap the original method
newMethods[name] = _.wrap(method);
// Mark the method
newMethods[name].collision = protectionType;
});
// Clone self making sure no collisions happen
const newStamp = this.compose();
// Overwrite old methods with the new methods
_.assign(newStamp.compose.methods, newMethods);
return newStamp;
},
protectMethodCollisions(...names) {
return this.methodCollisions('protect', ...names);
},
deferMethodCollisions(...names) {
return this.methodCollisions('defer', ...names);
}
},
composers: ({stamp, composables}) => {
const methodsMetadataArray = _(composables)
.map(c => isStamp(c) ? c.compose : c)
.map('methods')
.compact()
.value();
const methodsStore = {}; // all methods collected to arrays
_.forEach(methodsMetadataArray, methodsMetadataObject => {
_.toPairs(methodsMetadataObject).forEach(([methodName, method]) => {
methodsStore[methodName] = (methodsStore[methodName] || []).concat(method);
});
});
_.toPairs(methodsStore).forEach(([methodName, methods]) => {
if (methods.length === 1) {
// No conflicts at all
methodsStore[methodName] = methods[0];
} else if (methods.some(m => m.collision === 'protect')) {
// Something protected got collided
throw new Error(`Attempt to compose collision protected method "${methodName}"`)
} else if (methods.some(m => m.collision === 'defer')) {
// Wrap several methods to the one method.
// Retrieving original methods:
methods = _.flatten(methods.map(m => m.collidedMethods || m));
// Wrapping:
methodsStore[methodName] = function () {
for (let i = 0; i < methods.length; i++) {
methods[i].apply(this, arguments);
}
};
// Marking as deferred, and remembering what we have wrapped
methodsStore[methodName].collision = 'defer';
methodsStore[methodName].collidedMethods = methods;
} else {
// No collision set up. Thus last method wins.
methodsStore[methodName] = methods[methods.length - 1];
}
});
stamp.compose.methods = methodsStore;
}
});
const Dog = stampit({methods: {redraw(){ console.log('Dog'); }}}); // whatever stamp
const DogWithProtectedRedraw = Dog
.compose(MethodCollisions).protectMethodCollisions('redraw');
const Circle = stampit({methods: {redraw(){ console.log('Circle'); }}})
.compose(MethodCollisions).deferMethodCollisions('redraw');
const Triangle = stampit({methods: {redraw(){ console.log('Triangle'); }}})
.compose(MethodCollisions).deferMethodCollisions('redraw');
let s = stampit().compose(Circle); // all good
s = s.compose(Triangle); // all good
s = s.compose(DogWithProtectedRedraw); // THROWS !!!
const stampit = require('stampit');
const isStamp = require('stampit/isStamp');
const flatten = arr => arr.reduce((a, b) => a.concat(b), []);
const MethodCollisions = stampit({
statics: {
methodCollisions(protectionType, ...names) {
if (!this.compose.methods) return this.compose();
const newMethods = {};
names.forEach(name => {
const method = this.compose.methods[name];
if (typeof method !== 'function') return;
// Wrap the original method and
newMethods[name] = function () {
return method.apply(this, arguments);
};
// Mark the method
newMethods[name].collision = protectionType;
});
// Clone self making sure no collisions happen
const newStamp = this.compose();
// Overwrite old methods with the new methods
Object.assign(newStamp.compose.methods, newMethods);
return newStamp;
},
protectMethodCollisions(...names) {
return this.methodCollisions('protect', ...names);
},
deferMethodCollisions(...names) {
return this.methodCollisions('defer', ...names);
}
},
composers: ({stamp, composables}) => {
const methodsMetadataArray = composables
.map(c => isStamp(c) ? c.compose : c)
.map(d => d.methods)
.filter(m => m);
const methodsStore = {}; // all methods collected to arrays
methodsMetadataArray.forEach(methodsMetadataObject => {
Object.keys(methodsMetadataObject).forEach(methodName => {
const method = methodsMetadataObject[methodName];
methodsStore[methodName] = (methodsStore[methodName] || []).concat(method);
});
});
Object.keys(methodsStore).forEach(methodName => {
let methods = methodsStore[methodName];
if (methods.length === 1) {
// No conflicts at all
methodsStore[methodName] = methods[0];
} else if (methods.some(m => m.collision === 'protect')) {
// Something protected got collided
throw new Error(`Attempt to compose collision protected method "${methodName}"`)
} else if (methods.some(m => m.collision === 'defer')) {
// Wrap several methods to the one method.
// Retrieving original methods:
methods = flatten(methods.map(m => m.collidedMethods || m));
// Wrapping:
methodsStore[methodName] = function () {
for (let i = 0; i < methods.length; i++) {
methods[i].apply(this, arguments);
}
};
// Marking as deferred, and remembering what we have wrapped
methodsStore[methodName].collision = 'defer';
methodsStore[methodName].collidedMethods = methods;
} else {
// No collision set up. Thus last method wins.
methodsStore[methodName] = methods[methods.length - 1];
}
});
stamp.compose.methods = methodsStore;
}
});
const Dog = stampit({methods: {redraw(){ console.log('Dog'); }}}); // whatever stamp
const DogWithProtectedRedraw = Dog
.compose(MethodCollisions).protectMethodCollisions('redraw');
const Circle = stampit({methods: {redraw(){ console.log('Circle'); }}})
.compose(MethodCollisions).deferMethodCollisions('redraw');
const Triangle = stampit({methods: {redraw(){ console.log('Triangle'); }}})
.compose(MethodCollisions).deferMethodCollisions('redraw');
let s = stampit().compose(Circle); // all good
s = s.compose(Triangle); // all good
s = s.compose(DogWithProtectedRedraw); // THROWS !!!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment