pack.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /**
  2. * Module dependencies
  3. */
  4. var util = require('util');
  5. var path = require('path');
  6. var _ = require('@sailshq/lodash');
  7. var flaverr = require('flaverr');
  8. var getIsProductionWithoutDebug = require('./private/get-is-production-without-debug');
  9. var hashCustomUsageOpts = require('./private/hash-custom-usage-opts');
  10. var helpBuildMachine = require('./private/help-build-machine');
  11. var getMethodName = require('./get-method-name');
  12. /**
  13. * .pack()
  14. *
  15. * Build a Pack, either from the specified Node-Machine definitions or by loading modules
  16. * from the specified directory.
  17. *
  18. * > This method is used by default in the index.js file of generated machinepacks.
  19. *
  20. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  21. * @required {String|Dictionary?} options
  22. * Either the absolute path to the location of the modules to load & pack (see `dir` below)
  23. * -OR- a dictionary of options:
  24. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  25. * @property {Dictionary?} defs
  26. * An alternative to `dir` and `pkg`. If specified, this is a dictionary
  27. * of raw definitions, keyed by identity. (This makes auto-require irrelevant.)
  28. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  29. * @property {String?} name
  30. * If unspecified, `pkg.name` will be used.
  31. *
  32. * @property {String?} version
  33. * If unspecified, `pkg.version` will be used.
  34. *
  35. * @property {String?} description
  36. * If unspecified, `pkg.description` will be used.
  37. *
  38. * @property {String?} license
  39. * If unspecified, `pkg.license` will be used.
  40. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  41. * @property {String?} dir
  42. * The absolute path to the location of the modules to load & pack.
  43. * (If a relative path is specified, it will be resolved relative from the `pkg`)
  44. *
  45. * @property {Dictionary?} pkg
  46. * The package dictionary (i.e. what package.json exports).
  47. * Will be used for refining the directory to load modules from.
  48. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  49. * @property {Dictionary?} customize
  50. * ** THIS IS EFFECTIVELY PRIVATE, AND NOT DESIGNED FOR EXTERNAL USE! **
  51. * Custom usage options to apply to every Callable that is auto-built by .pack().
  52. * (This is a key part of `.pack()`'s recursive call to itself. This isn't necessarily
  53. * how these things are set from userland though-- the best way to do that is with
  54. * the .customize() method.)
  55. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  56. * @returns {Pack}
  57. * A Pack instance- basically a dictionary of Callables ("wet machines") with
  58. * camelCased keys ("method names"). Plus a few extra methods.
  59. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  60. * Example usage:
  61. * ```
  62. * foo = machine.pack({
  63. * name: 'Foo',
  64. * defs: {
  65. * 'do-something': {
  66. * implementationType: 'classical'
  67. * inputs: {
  68. * favoriteColor: { type: 'string', required: true },
  69. * },
  70. * fn: async function(){
  71. * console.log('did it');
  72. * }
  73. * }
  74. * }
  75. * })
  76. * .customize({
  77. * arginStyle: 'serial',
  78. * execStyle: 'immediate'
  79. * });
  80. *
  81. * await foo.doSomething('quickly');
  82. * ```
  83. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  84. */
  85. module.exports = function pack(optionsOrDir){
  86. // Normalize usage to determine actual options.
  87. // > e.g. if specified as a string, understand as `options.dir`, etc.
  88. var options;
  89. if (_.isString(optionsOrDir)) {
  90. options = { dir: optionsOrDir };
  91. } else if (optionsOrDir === undefined) {
  92. options = {};
  93. } else if (!_.isObject(optionsOrDir) || _.isFunction(optionsOrDir) || _.isArray(optionsOrDir)) {
  94. throw new Error('Usage error: `.pack()` expects a dictionary of options, but instead got:'+util.inspect(optionsOrDir, {depth:null}));
  95. } else {
  96. options = optionsOrDir;
  97. }
  98. // Usage sanity checks:
  99. if (options.pkg !== undefined) {
  100. if (!_.isObject(options.pkg) || _.isArray(options.pkg) || _.isFunction(options.pkg)) {
  101. throw new Error('Usage error: `.pack()` received an invalid `pkg`. If specified, `pkg` must be a dictionary, but instead got:'+util.inspect(options.pkg, {depth:null}));
  102. }
  103. }//fi
  104. if (options.dir !== undefined) {
  105. if (!_.isString(options.dir)) {
  106. throw new Error('Usage error: `.pack()` received a `dir` path which is not a string:'+util.inspect(options.dir, {depth:null}));
  107. }
  108. }//fi
  109. // Local variables to provide convenient access below.
  110. // Uses either direct key (`options.*`) or nested under pkg (`options.pkg.*`).
  111. var name = options.name !== undefined ? options.name : (options.pkg&&options.pkg.name);
  112. var version = options.version !== undefined ? options.version : (options.pkg&&options.pkg.version);
  113. var description = options.description !== undefined ? options.description : (options.pkg&&options.pkg.description);
  114. var license = options.license !== undefined ? options.license : (options.pkg&&options.pkg.license);
  115. // Use defs if any were provided.
  116. var defs = options.defs;
  117. // █████╗ ██╗ ██╗████████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗██╗██████╗ ███████╗
  118. // ██╔══██╗██║ ██║╚══██╔══╝██╔═══██╗ ██╔══██╗██╔════╝██╔═══██╗██║ ██║██║██╔══██╗██╔════╝
  119. // ███████║██║ ██║ ██║ ██║ ██║█████╗██████╔╝█████╗ ██║ ██║██║ ██║██║██████╔╝█████╗
  120. // ██╔══██║██║ ██║ ██║ ██║ ██║╚════╝██╔══██╗██╔══╝ ██║▄▄ ██║██║ ██║██║██╔══██╗██╔══╝
  121. // ██║ ██║╚██████╔╝ ██║ ╚██████╔╝ ██║ ██║███████╗╚██████╔╝╚██████╔╝██║██║ ██║███████╗
  122. // ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝
  123. //
  124. // If no defs were explicitly provided, then auto-require modules.
  125. if (defs === undefined) {
  126. // Make sure `dir` was specified.
  127. if (!options.dir) {
  128. throw flaverr({
  129. name: 'UsageError',
  130. message:
  131. 'Failed to .pack(). Since no `defs` were provided, tried to auto-load modules, but cannot.\n'+
  132. 'Please specify a `dir` (usually: `dir: __dirname`)'
  133. });
  134. }//•
  135. var explicitIdentitiesToLoad;
  136. if (options.pkg) {
  137. if (options.pkg&&options.pkg.machinepack&&options.pkg.machinepack.machines&&_.isArray(options.pkg.machinepack.machines)){
  138. explicitIdentitiesToLoad = options.pkg.machinepack.machines;
  139. } else {
  140. throw flaverr({
  141. name: 'ImplementationError', // formerly: {code: 'E_INVALID_OPTION'}
  142. message:
  143. 'Failed to .pack(). Provided `pkg.machinepack.machines` is invalid. '+
  144. '(Should be an array of strings.)'
  145. });
  146. }
  147. }//fi
  148. // If no explicit identities were provided, just try to load all `.js` files in `dir`.
  149. // (with the exception of `index.js`, which is reserved.)
  150. if (!explicitIdentitiesToLoad) {
  151. throw new Error('Invalid options: If `defs` is not provided, then both `dir` AND `pkg` must be set.');
  152. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  153. // FUTURE: MAYBE bring the following naive "load everything" approach back
  154. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  155. // // // Ensure we have an absolute path.
  156. // // options.dir = path.resolve(options.dir);
  157. // // // Load modules (as dry machine definitions)
  158. // // var inventory = includeAll({
  159. // // dirname: options.dir,
  160. // // filter: /(.+)\.js/,
  161. // // exclude: [
  162. // // /^index.js$/
  163. // // ],
  164. // // flatten: true
  165. // // });
  166. // // defs = _.reduce(inventory, function (memo, rawNMDef, key) {
  167. // // rawNMDef.identity = _.kebabCase(key);
  168. // // memo[rawNMDef.identity] = rawNMDef;
  169. // // return memo;
  170. // // }, {});
  171. // // </naive "load all">
  172. // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  173. // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  174. // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  175. // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  176. } else {
  177. // Resolve source directory path from `machineDir` option.
  178. // > Note that we tolerate mispellings and absence of the option, for backwards compat.
  179. var srcDir = options.pkg.machinepack.machineDir || options.pkg.machinepack.machinedir || '';
  180. if (!_.isString(srcDir)) {
  181. throw flaverr({
  182. name: 'ImplementationError',
  183. message:
  184. 'Provided `pkg.machinepack.machineDir` is invalid.\n'+
  185. 'If specified, should be a string-- the relative path to the directory\n'+
  186. 'where module definitions will be loaded from (e.g. "./lib" or "./machines").'
  187. });
  188. }//•
  189. // Load defs.
  190. defs = _.reduce(explicitIdentitiesToLoad, function (defs, identityToLoad) {
  191. var pathToRequire = path.resolve(options.dir, srcDir, identityToLoad);
  192. var def;
  193. try {
  194. def = require(pathToRequire);
  195. } catch (err) {
  196. if (flaverr.taste('MODULE_NOT_FOUND', err)) {
  197. throw flaverr({
  198. name: 'ImplementationError',
  199. code: 'MODULE_NOT_FOUND',// formerly: 'E_MACHINE_NOT_FOUND'
  200. raw: err,
  201. message:
  202. 'Could not find `'+identityToLoad+'`, one of the modules in `pkg.machinepack.machines`.\n'+
  203. 'Maybe it was deleted or mistyped?',
  204. });
  205. } else {
  206. throw flaverr({
  207. name: 'ImplementationError',
  208. code: 'E_INVALID_DEF',// formerly: 'E_INVALID_MACHINE'
  209. raw: err,
  210. message: 'Error loading `'+identityToLoad+'`, one of the modules in `pkg.machinepack.machines`.'
  211. });
  212. }
  213. }//>-
  214. defs[identityToLoad] = def;
  215. return defs;
  216. }, {});//∞
  217. }//fi (loaded using explicit identities)
  218. }//fi (had to auto-require)
  219. // ██╗███╗ ██╗███████╗████████╗ █████╗ ███╗ ██╗████████╗██╗ █████╗ ████████╗███████╗
  220. // ██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗████╗ ██║╚══██╔══╝██║██╔══██╗╚══██╔══╝██╔════╝
  221. // ██║██╔██╗ ██║███████╗ ██║ ███████║██╔██╗ ██║ ██║ ██║███████║ ██║ █████╗
  222. // ██║██║╚██╗██║╚════██║ ██║ ██╔══██║██║╚██╗██║ ██║ ██║██╔══██║ ██║ ██╔══╝
  223. // ██║██║ ╚████║███████║ ██║ ██║ ██║██║ ╚████║ ██║ ██║██║ ██║ ██║ ███████╗
  224. // ╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝
  225. //
  226. // ██████╗ █████╗ ██████╗██╗ ██╗
  227. // ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝
  228. // ██████╔╝███████║██║ █████╔╝
  229. // ██╔═══╝ ██╔══██║██║ ██╔═██╗
  230. // ██║ ██║ ██║╚██████╗██║ ██╗
  231. // ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
  232. //
  233. // Now set up the dictionary that hold our machines.
  234. var newPack = {};
  235. // Set up a place to cache customized sub-packs.
  236. // (See `customize()` below for more information.)
  237. var cachedCustomSubPacks = {};
  238. // Set up a home for default argins for this pack.
  239. // (See `configure()` below for more information.)
  240. //
  241. // Remember: These are NOT PROCESS-WIDE!
  242. // (Just pack-instance-wide)
  243. var defaultArgins = {};
  244. /**
  245. * .customize()
  246. *
  247. * Build a customized version of this pack, with machines re-built to work
  248. * using the specified custom usage options.
  249. * > If this exact customization has been used before for this pack,
  250. * > the customized pack will be _cloned and cached_. This works much
  251. * > like Node's core require() cache, and is designed to improve performance
  252. * > by avoiding unnecessarily duplicating work on a per-call basis.
  253. *
  254. * @param {Dictionary} customUsageOpts
  255. * @property {String?} arginStyle ("named" or "serial")
  256. * @property {String?} execStyle ("deferred" or "immediate")
  257. * … (For full reference of opts, see `buildWithCustomUsage()`)
  258. *
  259. * @returns {Ref} [a custom, spin-off duplicate of this pack w/ custom usage]
  260. */
  261. Object.defineProperty(newPack, 'customize', {
  262. enumerable: false,
  263. configurable: false,
  264. writable: true,
  265. value: function customize(customUsageOpts){
  266. if (!getIsProductionWithoutDebug()) {
  267. if (!customUsageOpts || _.isEqual(customUsageOpts, {})) { throw new Error('Consistency violation: Cannot call `.customize()` without providing any custom usage options! Please specify at least one option such as "arginStyle" or "execStyle".'); }
  268. if (!_.isObject(customUsageOpts) || _.isArray(customUsageOpts) || _.isFunction(customUsageOpts)) { throw new Error('Consistency violation: `.customize()` must be called with a dictionary of custom usage options.'); }
  269. if (customUsageOpts.def !== undefined) { throw new Error('Consistency violation: Cannot specify `def` when calling `.customize()` on a package! Instead provide options like "arginStyle" or "execStyle".'); }
  270. }//fi
  271. var hashed;
  272. try {
  273. // - - - - - - - - - - - - - - - - - - - - - - - -
  274. // FUTURE: Simplify away this try/catch block with something like:
  275. // ```
  276. // var hashCustomUsageOpts = parley.callable(require('./private/hash-custom-usage-opts'));
  277. // hash = hashCustomUsageOpts(customUsageOpts).tolerate('E_UNHASHABLE').now();
  278. // ```
  279. // Or potentially: («« although this approach is slower-- plus it's more convoluted...)
  280. // ```
  281. // hash = parley.deferred(hashCustomUsageOpts, undefined, [customUsageOpts]).tolerate('E_UNHASHABLE').now();
  282. // ```
  283. // - - - - - - - - - - - - - - - - - - - - - - - -
  284. hashed = hashCustomUsageOpts(customUsageOpts);
  285. } catch (err) {
  286. if (flaverr.taste('E_UNHASHABLE', err)) {
  287. // Just do nothing else and continue
  288. // (but leave `hashed` falsy)
  289. // - - - - - - - - - - - - - - - - - - - - - - - -
  290. // Note, we could also log a warning like
  291. // ```
  292. // console.warn('WARNING: Could not compute hash from provided custom usage options. (Building customization of pack without caching for now...but this will probably be slow at runtime!)');
  293. // ```
  294. // …but then again it's perfectly valid to do this,
  295. // and you can always cache this yourself in userland
  296. // if it's slow. So that's why we didn't use this
  297. // warning.
  298. // - - - - - - - - - - - - - - - - - - - - - - - -
  299. } else {
  300. throw err;
  301. }
  302. }//>-
  303. // Use cached customization, if possible.
  304. if (hashed && cachedCustomSubPacks[hashed]) {
  305. return cachedCustomSubPacks[hashed];
  306. }//-•
  307. // Prepare a custom sub-pack
  308. // (this makes a recursive call to .pack() to ensure things get expanded
  309. // consistently, and that we get all of the goodies like `.inspect()`,
  310. // `.toString()`, etc.)
  311. var thisPack = this;
  312. var customSubPack = pack({
  313. name: name,
  314. version: version,
  315. description: description,
  316. license: license,
  317. customize: customUsageOpts,
  318. defs: _.reduce(_.keys(defs), function(_shallowCopyOfDryDefs, identity) {
  319. var callable = thisPack[getMethodName(identity)];
  320. var def = callable.getDef();
  321. _shallowCopyOfDryDefs[def.identity] = def;
  322. return _shallowCopyOfDryDefs;
  323. }, {})
  324. });
  325. // Apply default argins (if there are any) to this custom sub-pack
  326. if (_.keys(defaultArgins).length > 0) {
  327. customSubPack.configure(defaultArgins);
  328. }
  329. // If possible, cache this customization to speed up the next time this
  330. // variation of `.customize()` gets called again.
  331. if (hashed) {
  332. cachedCustomSubPacks[hashed] = customSubPack;
  333. }
  334. return customSubPack;
  335. }//ƒ
  336. });//…)
  337. /**
  338. * .configure()
  339. *
  340. * Set default argins for this pack.
  341. *
  342. * Note that these are NOT PROCESS-WIDE!
  343. * (Just pack-instance-wide)
  344. *
  345. * (Also note that, if called more than once, the subsequent call shallow-extends
  346. * any previous default argins that were configured. However, if the new default
  347. * argins have a key with an undefined value, that key will be ignored.)
  348. */
  349. Object.defineProperty(newPack, 'configure', {
  350. enumerable: false,
  351. configurable: false,
  352. writable: true,
  353. value: function configure(_newDefaultArgins){
  354. // Strip keys w/ `undefined` as their value, for consistency.
  355. _.each(_.keys(_newDefaultArgins), function(inputCodeName){
  356. if (_newDefaultArgins[inputCodeName] === undefined) {
  357. delete _newDefaultArgins[inputCodeName];
  358. }
  359. });//∞
  360. // Then just do a normal, shallow extend.
  361. _.extend(defaultArgins, _newDefaultArgins);
  362. // Chainable
  363. return newPack;
  364. }
  365. });
  366. /**
  367. * .inspect()
  368. *
  369. * (Automatically invoked in Node.js environments when this is passed into `util.inspect()` or `console.log()`)
  370. *
  371. * > This property can be overridden.
  372. */
  373. Object.defineProperty(newPack, 'inspect', {
  374. enumerable: false,
  375. configurable: false,
  376. writable: true,
  377. value: function inspect(){
  378. return ''+
  379. '-----------------------------------------\n'+
  380. ' '+(name || 'anonymous package')+'\n'+
  381. (version ? ' v'+version : '')+(license ? ' ('+license+')\n' : version?'\n':'')+
  382. // (name ? ' (http://npmjs.com/package/'+name+')\n' : '')+
  383. ' \n'+
  384. (description ?
  385. (' '+description+'\n\n') :
  386. ''
  387. )+
  388. ' Methods:\n'+
  389. (
  390. _.map(_.keys(defs).sort(), function (nmIdentity){
  391. return ' · '+getMethodName(nmIdentity)+'()';
  392. }).join('\n')||
  393. ' (n/a)'
  394. )+'\n'+
  395. '-----------------------------------------\n';
  396. }//ƒ
  397. });//…)
  398. /**
  399. * .toString()
  400. *
  401. * (Automatically invoked before casting, string concatenation, etc.)
  402. *
  403. * > This property can be overridden.
  404. */
  405. Object.defineProperty(newPack, 'toString', {
  406. enumerable: false,
  407. configurable: false,
  408. writable: true,
  409. value: function toString(){
  410. return '[Package: '+(name||'anonymous')+']';
  411. }//ƒ
  412. });
  413. /**
  414. * .toJSON()
  415. *
  416. * Get a dry, JSON-compatible representation of this pack.
  417. *
  418. * Note that, if this "dry" pack representation is ACTUALLY JSON-stringified afterwards,
  419. * the stringification process will be lossy. Functions like `fn` or `custom` validators
  420. * are not actually JSON serializable. (To overcome this, use an additional layer of
  421. * serialization and deserialization such as rttc.dehydrate() and rttc.hydrate().)
  422. *
  423. * (Automatically invoked before JSON stringification when this is passed
  424. * into `JSON.stringify()`)
  425. */
  426. Object.defineProperty(newPack, 'toJSON', {
  427. enumerable: false,
  428. configurable: false,
  429. writable: true,
  430. value: function toJSON(){
  431. var thisPack = this;
  432. return {
  433. name: name||'anonymous',
  434. version: version||'',
  435. description: description||'',
  436. license: license||'',
  437. customize: options.customize||undefined,
  438. defs: _.reduce(_.keys(defs), function(_shallowCopyOfDryDefs, identity) {
  439. var callable = thisPack[getMethodName(identity)];
  440. var def = callable.getDef();
  441. _shallowCopyOfDryDefs[def.identity] = def;
  442. return _shallowCopyOfDryDefs;
  443. }, {})
  444. };
  445. }//ƒ
  446. });
  447. /**
  448. * .registerDefs()
  449. *
  450. * Attach a dictionary of new methods, keyed by identity.
  451. * Any existing properties with the same names will be overridden.
  452. *
  453. * > Note that this method must be used in order for new methods
  454. * > to work properly! Anything NOT attached this way but added
  455. * > as a property WILL be allowed (e.g. sub-packs), but it will
  456. * > be ignored as far as methods are concerned.
  457. *
  458. * @param {Dictionary} newMethodsByIdentity
  459. */
  460. Object.defineProperty(newPack, 'registerDefs', {
  461. enumerable: false,
  462. configurable: false,
  463. writable: true,
  464. value: function registerDefs(newMethodsByIdentity){
  465. // Attach new methods to `defs`.
  466. _.extend(defs, newMethodsByIdentity);
  467. // Build up a constant array of unconventional method names
  468. // (used below to show a warning if a machine identity looks too similar to native JS or Node stuff.)
  469. //
  470. // FUTURE: Merge in the rest from parley too
  471. var UNCONVENTIONAL_OR_RESERVED_METHOD_NAMES = [
  472. // In general:
  473. 'inspect', 'toString', 'valueOf', 'toLocaleString', 'toJSON',
  474. 'prototype', 'constructor',
  475. 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  476. // To watch out for with .pack() specifically:
  477. 'customize', 'configure', 'registerDefs'
  478. ];
  479. // Attach methods by building defs into callables.
  480. // (Along the way, set identities and auto-infer method names.)
  481. _.each(defs, function(def, identity){
  482. if (def.identity !== undefined && def.identity !== identity) {
  483. throw flaverr({
  484. name: 'UsageError',
  485. message: 'The definition under key "'+identity+'" has an inconsistent `identity` property: '+def.identity
  486. });
  487. }//•
  488. if (_.contains(UNCONVENTIONAL_OR_RESERVED_METHOD_NAMES, getMethodName(identity))) {
  489. console.warn('WARNING: `'+identity+'` is an unconventional or reserved identity. When converted to a method name (`'+getMethodName(identity)+'`), it could conflict with native features of JavaScript/Node.js, or otherwise interfere with native features of this runner. Please use a different identity instead. (Proceeding anyway this time...)');
  490. }
  491. var callable = helpBuildMachine(_.extend({}, options.customize||{}, {
  492. def: _.extend({ identity: identity }, def),
  493. defaultArgins: defaultArgins
  494. }));
  495. newPack[getMethodName(identity)] = callable;
  496. });//∞
  497. }//ƒ
  498. });//…)
  499. // ██████╗ ██╗ ██╗██╗██╗ ██████╗
  500. // ██╔══██╗██║ ██║██║██║ ██╔══██╗
  501. // ██████╔╝██║ ██║██║██║ ██║ ██║
  502. // ██╔══██╗██║ ██║██║██║ ██║ ██║
  503. // ██████╔╝╚██████╔╝██║███████╗██████╔╝
  504. // ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝
  505. //
  506. // ██╗███╗ ███╗██████╗ ██╗ ███████╗███╗ ███╗███████╗███╗ ██╗████████╗███████╗██████╗
  507. // ██║████╗ ████║██╔══██╗██║ ██╔════╝████╗ ████║██╔════╝████╗ ██║╚══██╔══╝██╔════╝██╔══██╗
  508. // ██║██╔████╔██║██████╔╝██║ █████╗ ██╔████╔██║█████╗ ██╔██╗ ██║ ██║ █████╗ ██║ ██║
  509. // ██║██║╚██╔╝██║██╔═══╝ ██║ ██╔══╝ ██║╚██╔╝██║██╔══╝ ██║╚██╗██║ ██║ ██╔══╝ ██║ ██║
  510. // ██║██║ ╚═╝ ██║██║ ███████╗███████╗██║ ╚═╝ ██║███████╗██║ ╚████║ ██║ ███████╗██████╔╝
  511. // ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═════╝
  512. //
  513. // ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗ ███████╗
  514. // ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗██╔════╝
  515. // ██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║███████╗
  516. // ██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║╚════██║
  517. // ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝███████║
  518. // ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
  519. //
  520. newPack.registerDefs(defs);
  521. return newPack;
  522. };