dox icon indicating copy to clipboard operation
dox copied to clipboard

[discussion] Overridable public API.

Open trusktr opened this issue 10 years ago • 9 comments

Based from discussions in #152 and #159, making the public API an adapter to a private API might be nice so that people can create "plugins" for the community by simply overriding some or all of dox's public methods.

To restore dox's functionality (i.e. to be able to use dox with multiple types of code in the same running program) I was thinking there could be a simple function that restores all the public methods to their original state simply by re-adapting to the internal API (not by reference, but by closure, for privacy).

trusktr avatar Mar 26 '15 00:03 trusktr

@trusktr @ChiperSoft I'm just wondering – have you seen such plugin architectures in the wild? It sounds awesomely simple, but I'm having second thoughts if it scales well.

I mean – as I see it, it would be unsafe to use two of such "plugins" at once.

tomek-he-him avatar Mar 26 '15 00:03 tomek-he-him

@tomekwi Good question! Can you think of a scenario where it might not scale?

trusktr avatar Mar 26 '15 01:03 trusktr

var dox0 = require("dox");
var dox1 = require("dox-css")(dox0);
var dox = require("dox-markdown")(dox1);

Suppose dox-css and dox-markdown both modify the same function. I would be a shot in the dark if one plugin is compatible with another.

tomek-he-him avatar Mar 26 '15 01:03 tomek-he-him

BTW, I've picked up this require("plugin")(require("base-lib")) pattern in gulp-help. It does its job well in simple cases.

tomek-he-him avatar Mar 26 '15 01:03 tomek-he-him

Having an explicit api for extension isn't nessisary. Simply changing dox to invoke functions via this instead of exports would make the lib extensible via mixins.

var dox = Object.assign(
  require('dox'),
  require('dox-css')
  require('dox-markdown')
);

Twipped avatar Mar 26 '15 03:03 Twipped

Good idea!

As a matter of fact, also with require("dox-css")(require("dox")) using this is the only thing required on dox side. The good part is, dox-css can access and extend original dox functions:

// dox-css/index.js
export default (originalDox) => {
  return Object.assign({}
    , originalDox

      // We want to extend `dox.parseComments`
    , { parseComments (...args) {
        // First we apply the original `dox` function
        let result = originalDox.parseComments(...args);
        // Here we can process the result
        return processedResult;
        }

      }
    );
  };

This way we leave dox untouched and extend its functions instead of overriding them (like decorators in python).

It would also solve the problem of scalability to some extent. As long as plugin authors extend functions this way, we can safely do:

let dox = require("dox-markdown")(
  require("dox-css")(
    require("dox")
    )
  )

– in this case dox-markdown extends functions of dox-css which themselves can be extended functions of dox.

tomek-he-him avatar Mar 26 '15 09:03 tomek-he-him

It would probably be better if extensions were added via Object.create and then used Object.getPrototypeOfto get at the original.

module.exports = exports = function (dox) {
  dox = Object.create(dox);
  return Object.assign(dox, exports);
}

exports.parseComments = function () {
  var result = Object.getPrototypeOf(this).parseComments.apply(this, arguments);
}

This way you're able to expose the new functions in case other extensions want to use them.

Twipped avatar Mar 26 '15 15:03 Twipped

Good point – that'd be lighter as well as the functions wouldn't need to be copied over and created.

Only the prototype chain might get quite deep – so you never really know if you're taking the real dox or an extended clone. In our example:

// dox-css/index.js
module.exports = exports = function (dox) {
  dox = Object.create(dox);  // This is indeed dox
  return Object.assign(dox, exports);
}

exports.parseComments = function () {
  var result = Object.getPrototypeOf(this).parseComments.apply(this, arguments);
    // dox.parseComments gets called.
}


// dox-markdown/index.js
module.exports = exports = function (dox) {
  dox = Object.create(dox);  // This is dox-css
  return Object.assign(dox, exports);
}

exports.parseComments = function () {
  var result = Object.getPrototypeOf(this).parseComments.apply(this, arguments);
    // doxCSS.parseComments gets called.
}

I don't think that's bad however. After all, every plugin will likely change one aspect of a function. No chance someone will use dox-markdown and dox-creole at the same time.

tomek-he-him avatar Mar 26 '15 16:03 tomek-he-him

:+1:

trusktr avatar Mar 27 '15 02:03 trusktr