Skip to content
This repository has been archived by the owner on May 23, 2022. It is now read-only.

Commit

Permalink
Adds reference documentation for the API.
Browse files Browse the repository at this point in the history
  • Loading branch information
robotlolita committed Feb 21, 2012
1 parent 7e5fbad commit c8c7250
Showing 1 changed file with 214 additions and 13 deletions.
227 changes: 214 additions & 13 deletions src/pandora.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,53 @@
// modularisation and a rather more declarative syntax.



//// -- Aliases --------------------------------------------------------
var inherit = Object.create
var proto = Object.getPrototypeOf
var keys = Object.keys

var slice = [].slice.call.bind([].slice)
var clone = with_mappings

var clone = from_mappings



//// -- Core interface -------------------------------------------------

///// Function pandora
// Returns a Pandora wrapper for an object.
//
// pandora :: Object -> Pandora
function pandora(service) {
return Pandora.make(service) }


///// Function merge
// Merges a series of Pandora wrappers into a single, native JavaScript
// object.
//
// Since keys can't collide and overrides must be explicit (and
// exclusive), the order in which arguments are passed over to this
// function doesn't matter.
//
// All of the requirements defined by the given Pandora wrappers *must*
// be met, otherwise an error is thrown.
//
//
// :raise: PandoraNotSatisfiedE
// If not all of the requirements for the given Pandora wrappers are
// met.
//
// :raise: PandoraCollisionE
// If more than one Pandora wrapper defines a given key, and neither
// provides an override clause for that key.
//
// :raise: PandoraOverrideCollisionE
// If more than one Pandora wrapper defines an override for a given
// key.
//
//
// marge :: Pandora... -> Object
function merge() {
var boxed = slice(arguments)
var required = []
Expand All @@ -61,18 +93,45 @@ function merge() {
copy_keys(module, result, overrides)
required.push.apply(required, module._required)
return result }
, { } )
, {} )

return !satisfied_p(result, required)? raise(PandoraNotSatisfiedE(required))
: /* otherwise */ result }



//// -- Helpers --------------------------------------------------------

///// Function satisfied_p
// Does the given `object' implement all of the required `names'?
//
// satisfied_p :: Object, [String] -> Bool
function satisfied_p(object, names) {
return names.every(function(key) {
return key in object }) }


///// Function copy_keys
// Copies the keys from an `origin' Pandora wrapper to the `target'
// Object.
//
// If the origin Pandora wrapper defines a key that already exists in
// the target object, it must explicitly declare that it overrides such
// a key. It's an error to declare duplicate keys, or duplicate
// overrides.
//
//
// :raise: PandoraCollisionE
// If the Pandora wrapper defines a key that already exists in the
// `target' Object.
//
// :raise: PandoraOverrideCollisionE
// If the Pandora wrapper defines an override that's already in
// effect in the `target' object — that is, such a key is in the set
// of overrides provided for this function.
//
//
// copy_keys! :: Pandora, target:Object*, { String -> Bool } -> target
function copy_keys(origin, target, overrides) {
var module = origin._value
keys(module).forEach(function(key) {
Expand All @@ -85,34 +144,113 @@ function copy_keys(origin, target, overrides) {

return target

////// Function override_p
// Checks if the origin defines an override for a given `key'.
//
// override_p :: String -> Bool
function override_p(key) {
if (origin._overrides[key] && overrides[key]) throw PandoraOverrideCollisionE(key)
return origin._overrides[key] }}


///// Function identity
// Returns the parameter it is given, as-is.
//
// identity :: a -> a
function identity(x) {
return x }


///// Function callable_p
// Does the given `subject' implement `[[Call]]`?
//
// callable_p :: a -> Bool
function callable_p(subject) {
return typeof subject == 'function' }


function with_properties(origin, names, mapping) {
///// Function from_properties
// Returns a new object containing only the given `names' from `origin'.
//
// The resulting properties can be, optionally, mapped through a
// `mapping' function.
//
// Note that the object is flattened, and the prototype chain is not
// maintained. Only own-enumerable keys will be copied over, if they are
// also present in the `names' list.
//
// from_properties :: Object, [String], (String -> String)? -> Object
function from_properties(origin, names, mapping) {
mapping = mapping || identity
return names.reduce( function(result, key) {
result[mapping(key)] = origin[key]
return result }
, {} )}


function with_mappings(origin, mapping) {
return with_properties(origin, keys(origin), mapping) }

///// Function from_mappings
// :convenience: from_properties
//
// Returns a new object where all of the keys are transformed by a
// `mapping' function.
//
// Note that the object is flattened, and the prototype chain is not
// maintained. Only own-enumerable keys will be copied over.
//
// This is a convenience method for calling `from_properties', where all
// of the keys should be copied over, rather than only a subset of it.
//
// from_mappings :: Object, (String -> String)? -> Object
function from_mappings(origin, mapping) {
return from_properties(origin, keys(origin), mapping) }



//// -- Core combinators -----------------------------------------------

///// Object Pandora
//
// A wrapper over a service object, which aims to provide useful
// combinators for a declarative definition of formal and composable
// modules.
//
// Basically, a wrapper holds a service object, which defines what kind
// of properties it exposes, a list of required properties, which
// defines what services *must* be implemented in the unwrapped module,
// and a set of explicit overrides, in which case those properties will
// be preferred should a key collision arise.
//
// When a wrapper is unwrapped by a call to the `value' method, that's
// equivalent to just passing the wrapper alone over to the `merge'
// call. All of the requirements must be met by the wrapper alone, and
// overrides have no effect — there can't be any collision!
//
// The combinators provided by this object allows one to expose a
// particular set of properties from the underlying service, as well as
// declare formal requirements and overrides.
//
// All of these combinators are pure. Not in the sense that they don't
// mutate the wrapper object, but rather, they don't mutate the
// underlying, wrapped service. They always return a reference to the
// instance calling the combinator.
//
//
// :Interface: Pandora
// -- The underlying service object being wrapped.
// _value :: Object
//
// -- The list of required properties this wrapper expects to be
// -- implemented.
// _required :: [String]
//
// -- The set of properties this wrapper explicitly overrides.
// _overrides :: {String -> Bool}
var Pandora = {

////// Function make
// Returns a new Pandora wrapper instance for the given `object'.
//
// make :: Object -> Pandora
make:
function _make(object) {
var instance = inherit(this)
Expand All @@ -124,44 +262,73 @@ var Pandora = {
return instance }


////// Function value
// Unwraps this Pandora instance, checking if the requirements are
// met.
//
// value :: @this:Pandora -> Object
, value:
function _value() {
return merge(this) }


////// Function only
// Defines a new service that exposes only the given names.
//
// only :: @this:Pandora*, String... -> this
, only:
function _only() {
this._value = with_properties(this._value, slice(arguments))
this._value = from_properties(this._value, slice(arguments))
return this }


////// Function hiding
// Defines a new service that exposes all but the given names.
//
// hiding :: @this:Pandora*, String... -> this
, hiding:
function _hiding() {
var names = slice(arguments)
names = keys(this._value).filter(function(key) {
return !~names.indexOf(key) })
this._value = with_properties(this._value, names)
this._value = from_properties(this._value, names)
return this }


////// Function prefix
// Defines a new service that prefixes all names with the given
// prefix.
//
// prefix :: @this:Pandora*, String -> this
, prefix:
function _prefix(name) {
this._value = with_mappings( this._value
this._value = from_mappings( this._value
, function(key){ return name + key })
return this }


////// Function aliasing
// Defines a new service where keys are transformed by the given
// mapping function or object.
//
// aliasing :: @this:Pandora*, (String -> String) -> this
// aliasing :: @this:Pandora*, {String -> String} -> this
, aliasing:
function _aliasing(map) {
var mapping = callable_p(map)? map
: /* otherwise */ function(key) {
return key in map? map[key]
: /* otherwise */ key }

this._value = with_mappings(this._value, mapping)
this._value = from_mappings(this._value, mapping)
return this }


////// Function map
// Defines a new service where *values* are transformed by the given
// mapping function.
//
// map :: @this:Pandora*, (a, String, {String -> a} -> a) -> this
, map:
function _map(mapping) {
var value = this._value
Expand All @@ -172,12 +339,27 @@ var Pandora = {
return this }


////// Function require
// Marks the given names as required.
//
// Required names *must* be implemented in the unwrapped module,
// otherwise it's an error.
//
// require :: @this:Pandora*, String... -> this
, require:
function _require() {
this._required.push.apply(this._required, arguments)
return this }


////// Function override
// Marks the given names as explicit overrides.
//
// Overrides allow Pandora to choose which service to choose when a
// key collision happens. Note that it's still an error for two
// different wrappers to define an override for the same key.
//
// override :: @this:Pandora*, String... -> this
, override:
function _override() {
slice(arguments).forEach( function(key) {
Expand All @@ -187,7 +369,13 @@ var Pandora = {
}



//// -- Error handling -------------------------------------------------

///// Function PandoraNotSatisfiedE
// Error factory for unsatisfied requirements in a Pandora unwrapping.
//
// PandoraNotSatisfiedE :: [String] -> Error
function PandoraNotSatisfiedE(required) {
var message = 'The following module\'s requirements haven\'t been met:\n - '
+ required.join('\n - ')
Expand All @@ -196,23 +384,36 @@ function PandoraNotSatisfiedE(required) {
return error }


///// Function PandoraCollisionE
// Error factory for key collisions in a Pandora unwrapping.
//
// PandoraCollisionE :: String -> Error
function PandoraCollisionE(key) {
var error = Error.call( inherit(Error.prototype)
, 'The key "' + key + '" already exists in the target module.')
error.name = 'PandoraCollisionE'
return error }


///// Function PandoraOverrideCollisionE
// Error factory for override collisiosn in a Pandora unwrapping.
//
// PandoraOverrideCollisionE :: String -> Error
function PandoraOverrideCollisionE(key) {
var error = Error.call( inherit(Error.prototype)
, 'The key "' + key + '" is already overriden by another module.')
error.name = 'PandoraOverrideCollisionE'
return error }


///// Function raise
// Helper function to allow throwing errors at the expression level.
//
// raise :: Error -> ()
function raise(error) { throw error }



//// -- Exports --------------------------------------------------------
module.exports = { pandora : pandora
, merge : merge
Expand All @@ -221,8 +422,8 @@ module.exports = { pandora : pandora
, internal: { identity : identity
, callable_p : callable_p
, satisfied_p : satisfied_p
, with_properties : with_properties
, with_mappings : with_mappings
, from_properties : from_properties
, from_mappings : from_mappings
, copy_keys : copy_keys

, raise : raise
Expand Down

0 comments on commit c8c7250

Please sign in to comment.