diff --git a/.jsdoc.json b/.jsdoc.json index 0ba42df0..08cb0127 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -28,18 +28,26 @@ ], "excludePattern": "(node_modules/|jsdocs)" }, - "templates": { - "cleverLinks": false, - "monospaceLinks": true - }, "opts": { + "template": "docdash-template", "destination": "./jsdocs/", "encoding": "utf8", "private": true, "recurse": true, - "template": "./node_modules/docdash", "sort": false }, + "templates": + { + "cleverLinks": false, + "monospaceLinks": true, + "default": + { + "staticFiles": + { + "include": [ "./docdash-template/favicon.ico"] + } + } + }, "docdash": { "static": true, "sort": false diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d4c3c9..5b9f8641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog - +## [v1.1.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.1.0) (2020-10-23) + - Bug Fix + - Owner of organization can access stack function + ## [v1.0.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.0.0) (2020-09-23) - Initial release for Contentstack CMA base JS management SDK diff --git a/README.md b/README.md index bb733ee5..a000d64f 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ contentstackClient.login({ email: 'EMAIL', password: 'PASSWORD'}) ### Management Token [Management Tokens](https://www.contentstack.com/docs/developers/create-tokens/about-management-tokens/) are **stack-level** tokens, with no users attached to them. ``` -contentstackClient.stack({ api_key: 'API_KEY', management_token: 'MANAGEMENT_TOKEN' }) +contentstackClient.stack({ api_key: 'API_KEY', management_token: 'MANAGEMENT_TOKEN' }).contentType('CONTENT_TYPE_UID') .fetch() -.then((stack) => { - console.log(stack) +.then((contenttype) => { + console.log(contenttype) }) ``` ### Contentstack Management JavaScript SDK: 5-minute Quickstart diff --git a/docdash-template/.gitignore b/docdash-template/.gitignore new file mode 100644 index 00000000..c70522de --- /dev/null +++ b/docdash-template/.gitignore @@ -0,0 +1,15 @@ +node_modules +fixtures-doc +*.sublime-project +*.sublime-workspace +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +logs +*.log +jspm_packages +.npm diff --git a/docdash-template/.travis.yml b/docdash-template/.travis.yml new file mode 100644 index 00000000..7acd0fb4 --- /dev/null +++ b/docdash-template/.travis.yml @@ -0,0 +1,20 @@ +language: node_js +node_js: +- '8' +- '10' +sudo: false +branches: + except: + - gh-pages +before_script: +- npm install +- npm install -g jsdoc +script: +- npm test +after_success: +- cd $TRAVIS_BUILD_DIR +- chmod +x generateDocs.sh +- bash generateDocs.sh +env: + global: + secure: SVc8CMwRHndoJpYUzfmmbj4mb0uvhTYFbQMqB2GNjKT9yFv5OpryNn7Yne16PD/MJ5zEtJ+QNuOYnQG+GDecTC5Ok9xKycgZyN7HA2+C6yE1u5C4o33XVTliW0aT7ADYra1V3jmP794SJSHALCwosJHAGxG6AH9cY/s4xfCuFE1TbwUOZA8WAxsu2tLvEp7IdoXLG3XGqwirSL95iC9VdfNcoF1uXdiJu1ZJw8/3RJUxQR6oDjQLsiv6rag83ktd0+Oe+nKw8qi4r1vRH6+SfZaEcaCy7qtZbNcwsE0CCZ3gwM3ekbrdnOMlOqz/Kgh7iTBlkfDpw6pVc3zaL9oagAbVJdbNiRWto8ourzaa4uAbqZJrJCl2auKRHLZTssu+jg7XI+oPhNsndhJZRDG0EvvDrbbSlVgCkTkgoucEcK/4HOt5b1m+5sd8343hDYBC4+26mhc5gzvojOQgVu+W+qgPMFf9Xt5LTTdPsKdNRPICFUy0h6R2FVfHmOYv2T42nm4I2NWXnpQffWsq2zHIuEiz0sBGDk27z7lzHwWnumJ98gPdYfGqSpWXzRPUoK80Jz/YIMop3yeiSY9GCHk9Lt8F548xQRhqQCzjmHo9wSmubfE5LHQCehxZ0zWrXzuLsxOWDh9NpKmM8dk5VV3ZBvqrMubNSBgpYp1YnGHFuRU= \ No newline at end of file diff --git a/docdash-template/CHANGELOG.md b/docdash-template/CHANGELOG.md new file mode 100644 index 00000000..14af8011 --- /dev/null +++ b/docdash-template/CHANGELOG.md @@ -0,0 +1,65 @@ +## Version 1.2.0 + +* [feature] host fonts locally +* [feature] separate styles for headers inside user markdown +* [feature] hide static/private method depending of the config +* [fix] fix empty source code lines in some browsers +* [fix] improved viewing theme on smaller screens + +## Version 1.1.1 + +* [feature] scroll to currently opened method on page load +* [fix] fixed searching in IE11 +* [fix] hiding/showing find exact match to open only single relevant section + +## Version 1.1.0 + +* [scripts] remove jQuery as dependency +* [feature] allow aliasing event names + +## Version 1.0.3 + +* [style] break headers into multiple lines +* [style] break links in descriptions into multiple lines +* [fix] fix ancestor check when there are none, like including tutorials +* [fix] remove unnecessary files from published package +* [fix] stop crashing on incorrect params JSDoc comments +* [feature] add displaying version from package.json when it is provided +* [feature] add support for yield +* [feature] add support for namepsaces that are functions +* [feature] add support for interfaces +* [feature] add support for modifies + +## Version 1.0.2 + +* [styles] increase space between custom menu items +* [option] Added `wrap` option to wrap long names instead of trimming them +* [option] Added `navLevel` option to control depth level to show in navbar, starting at 0 +* [option] Added `private` option to show/hide @private in navbar + +## Version 1.0.1 + +* Allow adding custom menu items +* Remove line-height: 160% + +## Version 1.0.0 + +* Add option to add disqus comments to each page +* Add option to filter through navigation items +* Add option to have menus collapsed by default and only open the one for the current page +* Add option to provide custom site title +* Add option to provide meta information for the website +* Add option to provide opengraph information for the website +* Add viewport meta data +* Added global table styles +* Added support for @hidecontainer (jsdoc 3.5.0) +* Added support for useLongnameInNav +* Allow including typedefs in the menu +* Allow inclusion of custom CSS +* Allow injecting external or local copied scripts into HTML +* Allow removing single and double quotes from pathnames +* Fix crash when @example is empty or undefined +* Fix issue with node 8.5 +* Fixing copyFile problem on some systems or nodejs versions +* Removes arbitrary width property +* Support ordering of the main navbar sections \ No newline at end of file diff --git a/docdash-template/LICENSE.md b/docdash-template/LICENSE.md new file mode 100644 index 00000000..ff66af58 --- /dev/null +++ b/docdash-template/LICENSE.md @@ -0,0 +1,61 @@ +# License + +Docdash is free software, licensed under the Apache License, Version 2.0 (the +"License"). Commercial and non-commercial use are permitted in compliance with +the License. + +Copyright (c) 2016 Clement Moron and the +[contributors to docdash](https://github.com/clenemt/docdash/graphs/contributors). +All rights reserved. + +You may obtain a copy of the License at: +http://www.apache.org/licenses/LICENSE-2.0 + +In addition, a copy of the License is included with this distribution. + +As stated in Section 7, "Disclaimer of Warranty," of the License: + +> Licensor provides the Work (and each Contributor provides its Contributions) +> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +> express or implied, including, without limitation, any warranties or +> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any risks +> associated with Your exercise of permissions under this License. + +The source code for docdash is available at: +https://github.com/clenemt/docdash + +# Third-Party Software + +Docdash includes or depends upon the following third-party software, either in +whole or in part. Each third-party software package is provided under its own +license. + +## JSDoc 3 + +JSDoc 3 is free software, licensed under the Apache License, Version 2.0 (the +"License"). Commercial and non-commercial use are permitted in compliance with +the License. + +Copyright (c) 2011-2016 Michael Mathews and the +[contributors to JSDoc](https://github.com/jsdoc3/jsdoc/graphs/contributors). +All rights reserved. + +You may obtain a copy of the License at: +http://www.apache.org/licenses/LICENSE-2.0 + +In addition, a copy of the License is included with this distribution. + +As stated in Section 7, "Disclaimer of Warranty," of the License: + +> Licensor provides the Work (and each Contributor provides its Contributions) +> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +> express or implied, including, without limitation, any warranties or +> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any risks +> associated with Your exercise of permissions under this License. + +The source code for JSDoc 3 is available at: +https://github.com/jsdoc3/jsdoc diff --git a/docdash-template/README.md b/docdash-template/README.md new file mode 100644 index 00000000..7ac4458c --- /dev/null +++ b/docdash-template/README.md @@ -0,0 +1,150 @@ +# Docdash +[![Build Status](https://api.travis-ci.org/clenemt/docdash.png?branch=master)](https://travis-ci.org/clenemt/docdash) [![npm version](https://badge.fury.io/js/docdash.svg)](https://badge.fury.io/js/docdash) [![license](https://img.shields.io/npm/l/docdash.svg)](LICENSE.md) + +A clean, responsive documentation template theme for JSDoc 3. + +![docdash-screenshot](https://cloud.githubusercontent.com/assets/447956/13398144/4dde7f36-defd-11e5-8909-1a9013302cb9.png) + +![docdash-screenshot-2](https://cloud.githubusercontent.com/assets/447956/13401057/e30effd8-df0a-11e5-9f51-66257ac38e94.jpg) + +## Example +See http://clenemt.github.io/docdash/ for a sample demo. :rocket: + +## Install + +```bash +$ npm install docdash +``` + +## Usage +Clone repository to your designated `jsdoc` template directory, then: + +```bash +$ jsdoc entry-file.js -t path/to/docdash +``` + +## Usage (npm) +In your projects `package.json` file add a new script: + +```json +"script": { + "generate-docs": "node_modules/.bin/jsdoc -c jsdoc.json" +} +``` + +In your `jsdoc.json` file, add a template option. + +```json +"opts": { + "template": "node_modules/docdash" +} +``` + +## Sample `jsdoc.json` +See the config file for the [fixtures](fixtures/fixtures.conf.json) or the sample below. + +```json +{ + "tags": { + "allowUnknownTags": false + }, + "source": { + "include": "../js", + "includePattern": "\\.js$", + "excludePattern": "(node_modules/|docs)" + }, + "plugins": [ + "plugins/markdown" + ], + "opts": { + "template": "assets/template/docdash/", + "encoding": "utf8", + "destination": "docs/", + "recurse": true, + "verbose": true + }, + "templates": { + "cleverLinks": false, + "monospaceLinks": false + } +} +``` + +## Options +Docdash supports the following options: + +```json5 +{ + "docdash": { + "static": [false|true], // Display the static members inside the navbar + "sort": [false|true], // Sort the methods in the navbar + "sectionOrder": [ // Order the main section in the navbar (default order shown here) + "Classes", + "Modules", + "Externals", + "Events", + "Namespaces", + "Mixins", + "Tutorials", + "Interfaces" + ], + "disqus": "", // Shortname for your disqus (subdomain during site creation) + "openGraph": { // Open Graph options (mostly for Facebook and other sites to easily extract meta information) + "title": "", // Title of the website + "type": "website", // Type of the website + "image": "", // Main image/logo + "site_name": "", // Site name + "url": "" // Main canonical URL for the main page of the site + }, + "meta": { // Meta information options (mostly for search engines that have not indexed your site yet) + "title": "", // Also will be used as postfix to actualy page title, prefixed with object/document name + "description": "", // Description of overal contents of your website + "keyword": "" // Keywords for search engines + }, + "search": [false|true], // Display seach box above navigation which allows to search/filter navigation items + "collapse": [false|true], // Collapse navigation by default except current object's navigation of the current page + "wrap": [false|true], // Wrap long navigation names instead of trimming them + "typedefs": [false|true], // Include typedefs in menu + "navLevel": [integer], // depth level to show in navbar, starting at 0 (false or -1 to disable) + "private": [false|true], // set to false to not show @private in navbar + "removeQuotes": [none|all|trim],// Remove single and double quotes, trim removes only surrounding ones + "scripts": [], // Array of external (or relative local copied using templates.default.staticFiles.include) js or css files to inject into HTML, + "menu": { // Adding additional menu items after Home + "Project Website": { // Menu item name + "href":"https://myproject.com", //the rest of HTML properties to add to manu item + "target":"_blank", + "class":"menu-item", + "id":"website_link" + }, + "Forum": { + "href":"https://myproject.com.forum", + "target":"_blank", + "class":"menu-item", + "id":"forum_link" + } + }, + scopeInOutputPath: [false|true], // Add scope from package file (if present) to the output path, true by default. + nameInOutputPath: [false|true], // Add name from package file to the output path, true by default. + versionInOutputPath: [false|true] // Add package version to the output path, true by default. + } +} +``` + +Place them anywhere inside your `jsdoc.json` file. + +## Contributors + +[![0](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/0)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/0) +[![1](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/1)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/1) +[![2](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/2)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/2) +[![3](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/3)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/3) +[![4](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/4)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/4) +[![5](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/5)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/5) +[![6](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/6)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/6) +[![7](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/images/7)](https://sourcerer.io/fame/ar2rsawseen/clenemt/docdash/links/7) + +## Thanks +Thanks to [lodash](https://lodash.com) and [minami](https://github.com/nijikokun/minami). + +## License +Licensed under the Apache License, version 2.0. (see [Apache-2.0](LICENSE.md)). diff --git a/docdash-template/favicon.ico b/docdash-template/favicon.ico new file mode 100644 index 00000000..a1c0f9fa Binary files /dev/null and b/docdash-template/favicon.ico differ diff --git a/docdash-template/fixtures/base/chains.js b/docdash-template/fixtures/base/chains.js new file mode 100644 index 00000000..725f145a --- /dev/null +++ b/docdash-template/fixtures/base/chains.js @@ -0,0 +1,70 @@ +"use strict"; +/** + * @fileOverview The chains define the primary composition elements (functions) that determine the order of execution. + * + * @module base/chains + * @requires dcl + */ +var dcl = require( "dcl" ); +/** + * @classDesc Chains define the primary composition elements (functions) that determine the order of execution. + * @exports base/chains + * @constructor + */ +var Chains = dcl( null, {declaredClass : "base/chains"} ); +/** + * The `close` method asks an object to shut itself down in a way that will allow it to be reopened, unlike the + * [end method]{@link base/chains#end} which will call the destroy method which should make the object unusable, but also + * devoid of all resources whereas `close` may still keep some resources open. + * + * | Heading 1 | Heading 2 | Heading 3 | + * |-----------|-----------|-----------------| + * | Bar | Food | This is a table | + * + * This uses the `before` chain which means the last one defined in the first one destroyed + * @memberOf base/chains# + * @name close + * @see base/chains#open + */ +dcl.chainBefore( Chains, "close" ); +/** + * The `end` method will call the destroy method which should make the object unusable and + * devoid of all resources, unlike the + * [close method]{@link base/chains#close} asks an object to shut itself down in a way that will allow it to be reopened. + * + * This uses the `before` chain which means the last one defined in the first one destroyed + * @memberOf base/chains# + * @name end + * + * @example Add *this* to your application.properties. + * {@lang bash} + * foo=bar + * + */ +dcl.chainBefore( Chains, "end" ); +/** + * Destroy is called by the end method and it is here that you should clean up after yourself. The difference between + * `destroy` and [end]{@link base/chains#end} is the `end` is the verb that you raise on an object to ask it to go away + * and `destroy` is where you actually do the work to clean up. Think of this as the counterpart of `constructor` yet + * not called automatically. + * + * This uses the `before` chain which means the last one defined is the first one destroyed + * @private + * @memberOf base/chains# + * @name destroy + */ +dcl.chainBefore( Chains, "destroy" ); + +/** + * If you are using the open/close paradigm for an object that can kind of go dormant on {@link base/chains#close} and can be "reopened" + * again later, here is where the "open" code will go. + * + * This used the `after` chain which means that the first one defined is the first one destroyed. + * + * @memberOf base/chains# + * @name open + * @see base/chains#close + */ +dcl.chainAfter( Chains, "open" ); + +module.exports = Chains; diff --git a/docdash-template/fixtures/base/index.js b/docdash-template/fixtures/base/index.js new file mode 100644 index 00000000..4e841713 --- /dev/null +++ b/docdash-template/fixtures/base/index.js @@ -0,0 +1,73 @@ +"use strict"; +/** + * @fileOverview This is base definition for all composed classes defined by the system + * @module base + * @requires base/chains + * @requires dcl + */ + +var dcl = require( "dcl" ); +var chains = require( "./chains" ); + +/** + * @classdesc The base of all classes in the system, this is one of the few pure "classes" in core the of the system. It is a + * pretty clean little class whose primary purpose is to surface the composition chains and a basis for storing + * options on mixin and subclass instances. Options are handled at the instance rather than the prototype level + * so that multiple instances don't compete for default values. + * + * @exports base + * @constructor + * @extends base/chains + */ +var Base = dcl( [chains], /** @lends base# */{ + declaredClass : "Base", + /** + * Add an option to a class. If any members of the hash already exist in `this.options`, they will be overwritten. + * @param {hash} options A hash of options you want to set + * @see {base#addDefaultOptions} + */ + addOptions : function ( options ) { + options = options || {}; + if ( this.options ) {options = sys.extend( {}, sys.result( this, 'options' ), options );} + this.options = options; + }, + /** + * Add a default option to a class. The default options are only set if there is not already a + * value for the option. + * @param {hash} options A hash of options you want to set + * @see {base#addOptions} + */ + addDefaultOptions : function ( options ) { + options = options || {}; + if ( this.options ) {options = sys.defaults( {}, sys.result( this, 'options' ), options );} + this.options = options; + }, + + /** + * Call this to close your object and dispose of all maintained resources. You can define this method on your + * own classes without having to call the superclass instance, however it is reccomended that you put + * all disposal code in `destroy()`. You must be disciplined about calling this on your instances. + * @see {base/chains#end} + * @see {base/chains#destroy} + */ + end : function () { + this.destroy() + }, + + /** + * Called when it is time to get rid of all of your instance level references and objects and events. You can + * define this method on your own classes without having to call the superclass instance. It is called by + * `instance.end()` automatically + * @see {base/chains#end} + * @see {base/chains#destroy} + */ + destroy : function () { + + } + + +} ); + +Base.compose = dcl; +Base.mixin = dcl.mix; +module.exports = Base; diff --git a/docdash-template/fixtures/documents/binder.js b/docdash-template/fixtures/documents/binder.js new file mode 100644 index 00000000..6d42813c --- /dev/null +++ b/docdash-template/fixtures/documents/binder.js @@ -0,0 +1,178 @@ +"use strict"; +/** + * @fileOverview allows you to bind a change watcher that looks for get and set operations on an arbitrary + * property of an object at at any depth. This allows you to look for changes or intercept values asynchronously or otherwise. + * @module documents/binder + * @requires async + * @requires documents/probe + * @requires lodash + * @requires promise + */ +var Promise = require( 'promise' ); +var async = require( "async" ); +var probe = require( "./probe" ); +var sys = require( "lodash" ); + +/** + * Identifies the properties that the binder expects + * @type {{getter: null, getterAsync: boolean, setter: null, validator: null, validatorAsync: boolean, setterAsync: boolean}} + * @private + */ +var dataBinderOptions = exports.dataBinderOptions = { + getter : null, + getterAsync : false, + setter : null, + validator : null, + validatorAsync : false, + setterAsync : false +}; + +/** + * You can unbind previously bound objects from here. + * + * @param {string} path The path that was bound using {@link module:documents/binder.bind} + * @param {*} record The object that was bound + */ +exports.unbind = function ( path, record ) { + var context = record; + var lastParent = context; + var parts = path.split( probe.delimiter ); + var lastPartName = path; + var lastParentName; + sys.each( parts, function ( part ) { + lastParentName = part; + lastParent = context; + context = context[part]; + lastPartName = part; + if ( sys.isNull( context ) || sys.isUndefined( context ) ) { + context = {}; + } + } ); + + if ( lastParent === context ) { + deleteBindings( record, lastPartName ); + } else { + deleteBindings( lastParent, lastPartName ); + } + + function deleteBindings( mountPoint, mountName ) { + mountPoint[mountName] = mountPoint["__" + mountName + "__"]; + delete mountPoint["__" + mountName + "__"]; + } +}; + +/** + * Bind to a property somewhere in an object. The property is found using dot notation and can be arbitrarily deep. + * @param {string} path The path into the object to locate the property. For instance this could be `"_id"`, `"name.last"`. + * or `"some.really.really.long.path.including.an.array.2.name"` + * @param {object} record Anything you can hang a property off of + * @param {options} options What you wanna do with the doohicky when yoyu bind it. + * @param {function(*):Promise|*=} options.getter This is the method to run when getting the value. When it runs, you will receive + * a single parameter which is the current value as the object understands it. You can return the value directly, just raise an event or + * whatever your little heart demands. However, if you are asynchronous, this will turn your return value into a promise, one of the + * few places this system will embrace promises over node-like error passing and that is mainly because this is a getter so a return value + * is particularly important. * + * @param {*} options.getter.value The current value of the record + * @param {function(err, value)=} options.getter.callback When asynchronous, return you value through this method using node style + * error passing (the promise is handled for you by this method). + * @param {boolean=} options.getterAsync When true (not truthy) the getter is treated asynchronously and returns a promise with your value. + * @param {function(*, *, *)=} options.setter A setter method + * @param {*} options.setter.newVal The new value + * @param {*} options.setter.oldVal The old value + * @param {*} options.setter.record The record hosting the change + * @param {function(*, *, *, function=)=} options.validator If you want a validator to run before settings values, pass this guy in + * @param {*} options.validator.newVal The new value + * @param {*} options.validator.oldVal The old value + * @param {*} options.validator.record The record hosting the change + * @param {function(err)=} options.validator.callback If the validator is asynchronous, then pass your value back here, otherwise pass it back as a return value. + * When you use an asynchronous instance, pass the error in the first value and then the rest of the parameters are yours to play with + * @param {boolean=} options.validatorAsync When true (not truthy) the validator is treated asynchornously and returns a promise with your value. + * @returns {*} + */ +exports.bind = function ( path, record, options ) { + options = sys.extend( {}, dataBinderOptions, options ); + var context = record; + var lastParent = context; + var parts = path.split( probe.delimiter ); + var lastPartName = path; + var lastParentName; + + sys.each( parts, function ( part ) { + lastParentName = part; + lastParent = context; + context = context[part]; + lastPartName = part; + if ( sys.isNull( context ) || sys.isUndefined( context ) ) { + context = {}; + } + } ); + + if ( lastParent === context ) { + setUpBindings( record, lastPartName ); + } else { + setUpBindings( lastParent, lastPartName ); + } + + function setUpBindings( mountPoint, mountName ) { + mountPoint["__" + mountName + "__"] = mountPoint[mountName]; + Object.defineProperty( mountPoint, mountName, { + get : function () { + if ( sys.isFunction( options.getter ) ) { + var promise; + if ( options.getterAsync === true ) { + promise = Promise.denodeify( options.getter ); + } + + if ( promise ) { + return promise( mountPoint["__" + mountName + "__"] ).then( function ( val ) { + mountPoint["__" + mountName + "__"] = val; + } ); + } else { + mountPoint["__" + mountName + "__"] = options.getter( mountPoint["__" + mountName + "__"] ); + return mountPoint["__" + mountName + "__"]; + } + + } else { + return mountPoint["__" + mountName + "__"]; + } + }, + set : function ( val ) { + async.waterfall( [ + function ( done ) { + if ( sys.isFunction( options.validator ) ) { + if ( options.validatorAsync ) { + options.validator( val, mountPoint["__" + mountName + "__"], record, done ); + } else { + var res = options.validator( val, mountPoint["__" + mountName + "__"], record ); + if ( res === true ) { + done(); + } else { + done( res ); + } + } + } else { + done(); + } + }, + function ( done ) { + if ( sys.isFunction( options.setter ) ) { + if ( options.setterAsync === true ) { + options.setter( val, mountPoint["__" + mountName + "__"], record, done ); + } else { + done( null, options.setter( val, mountPoint["__" + mountName + "__"], record ) ); + } + } else { + done( null, val ); + } + } + ], function ( err, newVal ) { + if ( err ) { throw new Error( err ); } + mountPoint["__" + mountName + "__"] = newVal; + } ); + + } + } ); + } + + return context; +}; diff --git a/docdash-template/fixtures/documents/collector.js b/docdash-template/fixtures/documents/collector.js new file mode 100644 index 00000000..aeb1ed22 --- /dev/null +++ b/docdash-template/fixtures/documents/collector.js @@ -0,0 +1,496 @@ +"use strict"; +/** + @fileOverview An object and array collector + @module ink/collector + */ + +var probe = require( "ink-probe" ); +var sys = require( "lodash" ); +var dcl = require( "dcl" ); + +/** + * A collector + * @constructor + */ +var CollectorBase = dcl( Destroyable, { + declaredClass : "CollectorBase", + constructor : function ( obj ) { + var that = this; + if ( obj && !sys.isObject( obj ) ) { + throw new TypeError( "Collectors require an initial object or array passed to the constructor" ); + } + /** + * The collection that being managed + * @type {object|array} + */ + this.heap = obj || {}; + // mixin the probe + probe.mixTo( this, this.heap ); + /** + * Get the size of the collection + * @name length + * @type {number} + * @memberOf module:documents/collector~CollectorBase# + */ + Object.defineProperty( this, "length", { + get : function () { + return sys.size( that.heap ); + } + } + ); + /** + * Creates an array of shuffled array values, using a version of the Fisher-Yates shuffle. + * See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * @function + * @memberOf module:documents/collector~CollectorBase# + * @returns {array} + */ + this.shuffle = sys.bind( sys.shuffle, this, this.heap ); + + }, + /** + * Adds an item to the collection + * @param {*} key The key to use for the item being added. + * @param {*} item The item to add to the collection. The item is not iterated so that you could add bundled items to the collection + */ + add : function ( key, item ) { + this.heap[key] = item; + }, + /** + * Iterate over each item in the collection, or a subset that matches a query. This supports two signatures: + * `.each(query, function)` and `.each(function)`. If you pass in a query, only the items that match the query + * are iterated over. + * @param {object=} query A query to evaluate + * @param {function(val, key)} iterator Function to execute against each item in the collection + * @param {object=} thisobj The value of `this` + */ + each : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + sys.each( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + sys.each( this.heap, query, thisobj ); + } + }, + /** + * Returns the collection as an array. If it is already an array, it just returns that. + * @return {array} + */ + toArray : function () { + return sys.toArray( this.heap ); + }, + /** + * Supports conversion to a JSON string or for passing over the wire + * @return {object} + * @returns {Object|array} + */ + toJSON : function () { + return this.heap; + }, + /** + * Maps the contents to an array by iterating over it and transforming it. You supply the iterator. Supports two signatures: + * `.map(query, function)` and `.map(function)`. If you pass in a query, only the items that match the query + * are iterated over. + * @param {object=} query A query to evaluate + * @param {function(val, key)} iterator Function to execute against each item in the collection + * @param {object=} thisobj The value of `this` + */ + map : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.map( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.map( this.heap, query, thisobj ); + } + }, + /** + * Reduces a collection to a value which is the accumulated result of running each element in the collection through the + * callback, where each successive callback execution consumes the return value of the previous execution. If accumulator + * is not passed, the first element of the collection will be used as the initial accumulator value. + * are iterated over. + * @param {object=} query A query to evaluate + * @param {function(result, val, key)} iterator The function that will be executed in each item in the collection + * @param {*=} accumulator Initial value of the accumulator. + * @param {object=} thisobj The value of `this` + * @return {*} + */ + reduce : function ( query, iterator, accumulator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.reduce( this.find( query ), iterator, accumulator, thisobj ); + } else { + thisobj = accumulator || this; + return sys.reduce( this.heap, query, iterator, thisobj ); + } + }, + /** + * Creates an object composed of keys returned from running each element + * of the collection through the given callback. The corresponding value of each key + * is the number of times the key was returned by the callback. + * @param {object=} query A query to evaluate. If you pass in a query, only the items that match the query + * are iterated over. + * @param {function(value, key, collection)} iterator + * @param {object=} thisobj The value of `this` + * @return {object} + */ + countBy : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.countBy( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.countBy( this.heap, query, thisobj ); + } + }, + /** + * Creates an object composed of keys returned from running each element of the collection through the callback. + * The corresponding value of each key is an array of elements passed to callback that returned the key. + * The callback is invoked with three arguments: (value, index|key, collection). + * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query + * are iterated over. + * @param {function(value, key, collection)} iterator + * @param {object=} thisobj The value of `this` + * @return {object} + */ + groupBy : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.groupBy( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.groupBy( this.heap, query, thisobj ); + } + }, + /** + * Reduce the collection to a single value. Supports two signatures: + * `.pluck(query, function)` and `.pluck(function)` + * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query + * are iterated over. + * @param {string} property The property that will be 'plucked' from the contents of the collection + * @return {*} + */ + pluck : function ( query, property ) { + if ( arguments.length === 2 ) { + return sys.map( this.find( query ), function ( record ) { + return probe.get( record, property ); + } ); + } else { + return sys.map( this.heap, function ( record ) { + return probe.get( record, query ); + } ); + } + }, + /** + * Returns a sorted copy of the collection. + * @param {object=} query The query to evaluate. If you pass in a query, only the items that match the query + * are iterated over. + * @param {function(value, key)} iterator + * @param {object=} thisobj The value of `this` + * @return {array} + */ + sortBy : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.sortBy( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.sortBy( this.heap, query, thisobj ); + } + }, + /** + * Retrieves the maximum value of an array. If callback is passed, + * it will be executed for each value in the array to generate the criterion by which the value is ranked. + * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query + * are iterated over. + * @param {function(value, key, collection)} iterator + * @param {object=} thisobj The value of `this` + * @return {number} + */ + max : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.max( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.max( this.heap, query, thisobj ); + } + }, + /** + * Retrieves the minimum value of an array. If callback is passed, + * it will be executed for each value in the array to generate the criterion by which the value is ranked. + * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query + * are iterated over. + * @param {function(value, key, collection)} iterator + * @param {object=} thisobj The value of `this` + * @return {number} + */ + min : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.min( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.min( this.heap, query, thisobj ); + } + }, + /** + * Destructor called when the object is destroyed. + */ + destroy : function () { + this.heap = null; + } +} ); + +/** + * An object based collector + * @extends module:documents/collector~CollectorBase + * @constructor + */ +var OCollector = dcl( CollectorBase, { + /** + * Get a record by key + * @param {*} key The key of the record to get + * @return {*} + */ + key : function ( key ) { + return this.heap[key]; + } +} ); + +//noinspection JSCommentMatchesSignature +/** + An array based collector + @extends module:documents/collector~CollectorBase + @constructor + */ +var ACollector = dcl( CollectorBase, { + constructor : function ( obj ) { + if ( obj && !sys.isArray( obj ) ) { + throw new TypeError( "Collectors require an array passed to the constructor" ); + } + this.heap = obj || []; + /** + * Creates an array of array elements not present in the other arrays using strict equality for comparisons, i.e. ===. + * @returns {array} + */ + this.difference = sys.bind( sys.difference, this, this.heap ); + /** + * This method gets all but the first values of array + * @param {number=} n The numer of items to return + * @returns {*} + */ + this.tail = sys.bind( sys.tail, this, this.heap ); + /** + * Gets the first n values of the array + * @param {number=} n The numer of items to return + * @returns {*} + */ + this.head = sys.bind( sys.head, this, this.heap ); + }, + /** + * Adds to the top of the collection + * @param {*} item The item to add to the collection. Only one item at a time can be added + */ + add : function ( item ) { + this.heap.unshift( item ); + }, + /** + * Add to the bottom of the list + * @param {*} item The item to add to the collection. Only one item at a time can be added + */ + append : function ( item ) { + this.heap.push( item ); + }, + /** + * Add an item to the top of the list. This is identical to `add`, but is provided for stack semantics + * @param {*} item The item to add to the collection. Only one item at a time can be added + */ + push : function ( item ) { + this.add( item ); + }, + /** + * Modifies the collection with all falsey values of array removed. The values false, null, 0, "", undefined and NaN are all falsey. + */ + compact : function () { + this.heap = sys.compact( this.heap ); + }, + /** + * Creates an array of elements from the specified indexes, or keys, of the collection. Indexes may be specified as + * individual arguments or as arrays of indexes + * @param {indexes} args The indexes to use + */ + at : function () { + var arr = sys.toArray( arguments ); + arr.unshift( this.heap ); + return sys.at.apply( this, arr ); + }, + /** + * Flattens a nested array (the nesting can be to any depth). If isShallow is truthy, array will only be flattened a single level. + * If callback is passed, each element of array is passed through a callback before flattening. + * @param {object=} query A query to evaluate . If you pass in a query, only the items that match the query + * are iterated over. + * @param {function(value, key, collection)} iterator, + * @param {object=} thisobj The value of `this` + * @return {number} + */ + flatten : function ( query, iterator, thisobj ) { + if ( sys.isPlainObject( query ) ) { + thisobj = thisobj || this; + return sys.flatten( this.find( query ), iterator, thisobj ); + } else { + thisobj = iterator || this; + return sys.flatten( this.heap, query, thisobj ); + } + }, + /** + * Gets an items by its index + * @param {number} key The index to get + * @return {*} + */ + index : function ( index ) { + return this.heap[ index ]; + } + } +); + +/** + Collect an object + @param {array|object} obj What to collect + @return {ACollector|OCollector} + */ +exports.collect = function ( obj ) { + if ( sys.isArray( obj ) ) { + return new ACollector( obj ); + } else { + return new OCollector( obj ); + } +}; + +exports.array = function ( obj ) { + return new ACollector( obj ); +}; + +exports.object = function ( obj ) { + return new OCollector( obj ); +}; + +/** + Returns true if all items match the query. Aliases as `all` + @function + + @param {object} qu The query to execute + @returns {boolean} + @name every + @memberOf module:documents/collector~CollectorBase# + */ + + +/** + Returns true if any of the items match the query. Aliases as `any` + @function + + @param {object} qu The query to execute + @returns {boolean} + @memberOf module:documents/collector~CollectorBase# + @name some + */ + + +/** + Returns the set of unique records that match a query + + @param {object} qu The query to execute. + @return {array} + @memberOf module:documents/collector~CollectorBase# + @name unique + @method + **/ + +/** + Returns true if all items match the query. Aliases as `every` + @function + + @param {object} qu The query to execute + @returns {boolean} + @name all + @memberOf module:documents/collector~CollectorBase# + */ + + +/** + Returns true if any of the items match the query. Aliases as `all` + @function + + @param {object} qu The query to execute + @returns {boolean} + @memberOf module:documents/collector~CollectorBase# + @name any + */ + + +/** + Remove all items in the object/array that match the query + + @param {object} qu The query to execute. See {@link module:ink/probe.queryOperators} for the operators you can use. + @return {object|array} The array or object as appropriate without the records. + @memberOf module:documents/collector~CollectorBase# + @name remove + @method + **/ + +/** + Returns the first record that matches the query and returns its key or index depending on whether `obj` is an object or array respectively. + Aliased as `seekKey`. + + @param {object} qu The query to execute. + @returns {object} + @memberOf module:documents/collector~CollectorBase# + @name findOneKey + @method + */ + + +/** + Returns the first record that matches the query. Aliased as `seek`. + + @param {object} qu The query to execute. + @returns {object} + @memberOf module:documents/collector~CollectorBase# + @name findOne + @method + */ + + +/** + Find all records that match a query and returns the keys for those items. This is similar to {@link module:ink/probe.find} but instead of returning + records, returns the keys. If `obj` is an object it will return the hash key. If 'obj' is an array, it will return the index + + @param {object} qu The query to execute. + @returns {array} + @memberOf module:documents/collector~CollectorBase# + @name findKeys + @method + */ + + +/** + Find all records that match a query + + @param {object} qu The query to execute. + @returns {array} The results + @memberOf module:documents/collector~CollectorBase# + @name find + @method + **/ + +/** + Updates all records in obj that match the query. See {@link module:ink/probe.updateOperators} for the operators that are supported. + + @param {object} qu The query which will be used to identify the records to updated + @param {object} setDocument The update operator. See {@link module:ink/probe.updateOperators} + @memberOf module:documents/collector~CollectorBase# + @name update + @method + */ diff --git a/docdash-template/fixtures/documents/model.js b/docdash-template/fixtures/documents/model.js new file mode 100644 index 00000000..3b5a84ea --- /dev/null +++ b/docdash-template/fixtures/documents/model.js @@ -0,0 +1,117 @@ +"use strict"; +/** + * @fileOverview A model is the first level if usable data-bearing entity in the system. It does NOT include any verbs for saving or anything like + * that, it is a pure, in memory data container + * @module documents/model + * @require base + * @require documents/probe + * @require lodash + */ + +var Base = require( "../base" ); +var probe = require( "./probe" ); +var sys = require( "lodash" ); +/** + * A model is the first level if usable data-bearing entity in the system. It does NOT include any verbs for saving or anything like + * that, it is a pure, in memory data container + * @exports documents/model + * @constructor + * @borrows module:documents/probe.get as get + * @borrows module:documents/probe.set as set + * @borrows module:documents/probe.any as any + * @borrows module:documents/probe.all as all + * @borrows module:documents/probe.remove as remove + * @borrows module:documents/probe.seekKey as seekKey + * @borrows module:documents/probe.seek as seek + * @borrows module:documents/probe.findOne as findOne + * @borrows module:documents/probe.findOneKey as findOneKey + * @borrows module:documents/probe.findKeys as findKeys + * @borrows module:documents/probe.find as find + * @borrows module:documents/probe.update as update + * @borrows module:documents/probe.some as some + * @borrows module:documents/probe.every as every + */ +var Model = Base.compose( [Base], /** @lends documents/model# */{ + constructor : function () { + var that = this; + probe.mixin( this ); + + var idField = "_id"; + /** + * The name of the field that uniquely identifies a record. When provided, some operations will take advantage of it + * + * @name _idField + * @memberOf documents/model# + * @type {string} + * @private + */ + Object.defineProperty( this, "_idField", { + get : function () { + return idField; + }, + set : function ( val ) { + idField = val; + }, + configurable : false, + enumerable : true, + writable : true + } ); + + /** + * The value of the primary key if {@link documents/model#_idField} is filled in. It will be null if none found + * + * @name _pkey + * @memberOf documents/model# + * @type {*} + * @private + */ + Object.defineProperty( this, "_pkey", { + get : function () { + var val; + if ( !sys.isEmpty( that._idField ) ) { + val = that[that._idField]; + } + return val; + }, + set : function ( val ) { + if ( !sys.isEmpty( that._idField ) ) { + that[that._idField] = val; + } + }, + configurable : false, + enumerable : true, + writable : true + } ); + + /** + * If {@link documents/model#_idField} is filled in and it's value is empty this will be true. + * @type {boolean} + * @name isNew + * @memberOf documents/model# + */ + Object.defineProperty( this, "isNew", { + get : function () { + return !sys.isEmpty( that._idField ) && !sys.isEmpty( that[that._idField] ) + }, + configurable : false, + enumerable : true, + writable : false + } ); + + /** + * Returns true if this instance is empty + * @type {boolean} + * @name isEmpty + * @memberOf documents/model# + */ + Object.defineProperty( this, "isEmpty", { + get : function () { + return sys.isEmpty( that ); + }, + configurable : false, + enumerable : true, + writable : false + } ); + } +} ); +module.exports = Model; diff --git a/docdash-template/fixtures/documents/probe.js b/docdash-template/fixtures/documents/probe.js new file mode 100644 index 00000000..60c7feab --- /dev/null +++ b/docdash-template/fixtures/documents/probe.js @@ -0,0 +1,1013 @@ +"use strict"; +/** + @fileOverview Queries objects in memory using a mongo-like notation for reaching into objects and filtering for records + + @module documents/probe + @author Terry Weiss + @license MIT + @requires lodash + */ + +var sys = require( "lodash" ); +/** + The list of operators that are nested within the expression object. These take the form {path:{operator:operand}} + @private + @type {array.} + **/ +var nestedOps = ["$eq", "$gt", "$gte", "$in", "$lt", "$lte", "$ne", "$nin", "$exists", "$mod", "$size", "$all"]; + +/** + The list of operators that prefix the expression object. These take the form {operator:{operands}} or {operator: [operands]} + @private + @type {array.} + **/ +var prefixOps = ["$and", "$or", "$nor", "$not"]; + +/** + Processes a nested operator by picking the operator out of the expression object. Returns a formatted object that can be used for querying + @private + @param {string} path The path to element to work with + @param {object} operand The operands to use for the query + @return {object} A formatted operation definition + **/ +function processNestedOperator( path, operand ) { + var opKeys = Object.keys( operand ); + return { + operation : opKeys[ 0 ], + operands : [operand[ opKeys[ 0 ] ]], + path : path + }; +} + +/** + Interrogates a single query expression object and calls the appropriate handler for its contents + @private + @param {object} val The expression + @param {object} key The prefix + @returns {object} A formatted operation definition + **/ +function processExpressionObject( val, key ) { + var operator; + if ( sys.isObject( val ) ) { + var opKeys = Object.keys( val ); + var op = opKeys[ 0 ]; + + if ( sys.indexOf( nestedOps, op ) > -1 ) { + operator = processNestedOperator( key, val ); + } else if ( sys.indexOf( prefixOps, key ) > -1 ) { + operator = processPrefixOperator( key, val ); + } else if ( op === "$regex" ) { + // special handling for regex options + operator = processNestedOperator( key, val ); + } else if ( op === "$elemMatch" ) { + // elemMatch is just a weird duck + operator = { + path : key, + operation : op, + operands : [] + }; + sys.each( val[ op ], function ( entry ) { + operator.operands = parseQueryExpression( entry ); + } ); + } + else { + throw new Error( "Unrecognized operator" ); + } + } else { + operator = processNestedOperator( key, { $eq : val } ); + } + return operator; +} + +/** + Processes a prefixed operator and then passes control to the nested operator method to pick out the contained values + @private + @param {string} operation The operation prefix + @param {object} operand The operands to use for the query + @return {object} A formatted operation definition + **/ +function processPrefixOperator( operation, operand ) { + var component = { + operation : operation, + path : null, + operands : [] + }; + + if ( sys.isArray( operand ) ) { + //if it is an array we need to loop through the array and parse each operand + //if it is an array we need to loop through the array and parse each operand + sys.each( operand, function ( obj ) { + sys.each( obj, function ( val, key ) { + component.operands.push( processExpressionObject( val, key ) ); + } ); + } ); + } else { + //otherwise it is an object and we can parse it directly + sys.each( operand, function ( val, key ) { + component.operands.push( processExpressionObject( val, key ) ); + } ); + } + return component; + +} + +/** + Parses a query request and builds an object that can used to process a query target + @private + @param {object} obj The expression object + @returns {object} All components of the expression in a kind of execution tree + **/ + +function parseQueryExpression( obj ) { + if ( sys.size( obj ) > 1 ) { + var arr = sys.map( obj, function ( v, k ) { + var entry = {}; + entry[k] = v; + return entry; + } ); + obj = { + $and : arr + }; + } + var payload = []; + sys.each( obj, function ( val, key ) { + + var exprObj = processExpressionObject( val, key ); + + if ( exprObj.operation === "$regex" ) { + exprObj.options = val.$options; + } + + payload.push( exprObj ); + } ); + + return payload; +} + +/** + The delimiter to use when splitting an expression + @type {string} + @static + @default '.' + **/ + +exports.delimiter = '.'; + +/** + Splits a path expression into its component parts + @private + @param {string} path The path to split + @returns {array} + **/ + +function splitPath( path ) { + return path.split( exports.delimiter ); +} + +/** + Reaches into an object and allows you to get at a value deeply nested in an object + @private + @param {array} path The split path of the element to work with + @param {object} record The record to reach into + @return {*} Whatever was found in the record + **/ +function reachin( path, record ) { + var context = record; + var part; + var _i; + var _len; + + for ( _i = 0, _len = path.length; _i < _len; _i++ ) { + part = path[_i]; + context = context[part]; + if ( sys.isNull( context ) || sys.isUndefined( context ) ) { + break; + } + } + + return context; +} + +/** + This will write the value into a record at the path, creating intervening objects if they don't exist + @private + @param {array} path The split path of the element to work with + @param {object} record The record to reach into + @param {string} setter The set command, defaults to $set + @param {object} newValue The value to write to the, or if the operator is $pull, the query of items to look for + */ +function pushin( path, record, setter, newValue ) { + var context = record; + var parent = record; + var lastPart = null; + var _i; + var _len; + var part; + var keys; + + for ( _i = 0, _len = path.length; _i < _len; _i++ ) { + part = path[_i]; + lastPart = part; + parent = context; + context = context[part]; + if ( sys.isNull( context ) || sys.isUndefined( context ) ) { + parent[part] = {}; + context = parent[part]; + } + } + + if ( sys.isEmpty( setter ) || setter === '$set' ) { + parent[lastPart] = newValue; + return parent[lastPart]; + } else { + switch ( setter ) { + case '$inc': + /** + * Increments a field by the amount you specify. It takes the form + * `{ $inc: { field1: amount } }` + * @name $inc + * @memberOf module:documents/probe.updateOperators + * @example + * var probe = require("documents/probe"); + * probe.update( obj, {'name.last' : 'Owen', 'name.first' : 'LeRoy'}, + * {$inc : {'password.changes' : 2}} ); + */ + + if ( !sys.isNumber( newValue ) ) { + newValue = 1; + } + if ( sys.isNumber( parent[lastPart] ) ) { + parent[lastPart] = parent[lastPart] + newValue; + return parent[lastPart]; + } + break; + case '$dec': + /** + * Decrements a field by the amount you specify. It takes the form + * `{ $dec: { field1: amount }` + * @name $dec + * @memberOf module:documents/probe.updateOperators + * @example + * var probe = require("documents/probe"); + * probe.update( obj, {'name.last' : 'Owen', 'name.first' : 'LeRoy'}, + * {$dec : {'password.changes' : 2}} ); + */ + + if ( !sys.isNumber( newValue ) ) { + newValue = 1; + } + if ( sys.isNumber( parent[lastPart] ) ) { + parent[lastPart] = parent[lastPart] - newValue; + return parent[lastPart]; + } + break; + case '$unset': + /** + * Removes the field from the object. It takes the form + * `{ $unset: { field1: "" } }` + * @name $unset + * @memberOf module:documents/probe.updateOperators + * @example + * var probe = require("documents/probe"); + * probe.update( data, {'name.first' : 'Yogi'}, {$unset : {'name.first' : ''}} ); + */ + + return delete parent[lastPart]; + case '$pop': + /** + * The $pop operator removes the first or last element of an array. Pass $pop a value of 1 to remove the last element + * in an array and a value of -1 to remove the first element of an array. This will only work on arrays. Syntax: + * `{ $pop: { field: 1 } }` or `{ $pop: { field: -1 } }` + * @name $pop + * @memberOf module:documents/probe.updateOperators + * @example + * var probe = require("documents/probe"); + * // attr is the name of the array field + * probe.update( data, {_id : '511d18827da2b88b09000133'}, {$pop : {attr : 1}} ); + */ + + if ( sys.isArray( parent[lastPart] ) ) { + if ( !sys.isNumber( newValue ) ) { + newValue = 1; + } + if ( newValue === 1 ) { + return parent[lastPart].pop(); + } else { + return parent[lastPart].shift(); + } + } + break; + case '$push': + /** + * The $push operator appends a specified value to an array. It looks like this: + * `{ $push: { : } }` + * @name $push + * @memberOf module:documents/probe.updateOperators + * @example + * var probe = require("documents/probe"); + * // attr is the name of the array field + * probe.update( data, {_id : '511d18827da2b88b09000133'}, + * {$push : {attr : {"hand" : "new", "color" : "new"}}} ); + */ + + if ( sys.isArray( parent[lastPart] ) ) { + return parent[lastPart].push( newValue ); + } + break; + case '$pull': + /** + * The $pull operator removes all instances of a value from an existing array. It looks like this: + * `{ $pull: { field: } }` + * @name $pull + * @memberOf module:documents/probe.updateOperators + * @example + * var probe = require("documents/probe"); + * // attr is the name of the array field + * probe.update( data, {'email' : 'EWallace.43@fauxprisons.com'}, + * {$pull : {attr : {"color" : "green"}}} ); + */ + + if ( sys.isArray( parent[lastPart] ) ) { + keys = exports.findKeys( parent[lastPart], newValue ); + sys.each( keys, function ( val, index ) { + return delete parent[lastPart][index]; + } ); + parent[lastPart] = sys.compact( parent[lastPart] ); + return parent[lastPart]; + } + } + } +} + +/** + The query operations that evaluate directly from an operation + @private + **/ +var operations = { + /** + * `$eq` performs a `===` comparison by comparing the value directly if it is an atomic value. + * otherwise if it is an array, it checks to see if the value looked for is in the array. + * `{field: value}` or `{field: {$eq : value}}` or `{array: value}` or `{array: {$eq : value}}` + * @name $eq + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {categories : "cat1"} ); + * // is the same as + * probe.find( data, {categories : {$eq: "cat1"}} ); + */ + + $eq : function ( qu, value ) { + if ( sys.isArray( value ) ) { + return sys.find( value, function ( entry ) { + return JSON.stringify( qu.operands[0] ) === JSON.stringify( entry ); + } ) !== void 0; + } else { + return JSON.stringify( qu.operands[0] ) === JSON.stringify( value ); + } + }, + /** + * `$ne` performs a `!==` comparison by comparing the value directly if it is an atomic value. Otherwise, if it is an array + * this is performs a "not in array". + * '{field: {$ne : value}}` or '{array: {$ne : value}}` + * @name $ne + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"name.first" : {$ne : "Sheryl"}} ); + */ + + $ne : function ( qu, value ) { + if ( sys.isArray( value ) ) { + return sys.find( value, function ( entry ) { + return JSON.stringify( qu.operands[0] ) !== JSON.stringify( entry ); + } ) !== void 0; + } else { + return JSON.stringify( qu.operands[0] ) !== JSON.stringify( value ); + } + }, + /** + * `$all` checks to see if all of the members of the query are included in an array + * `{array: {$all: [val1, val2, val3]}}` + * @name $all + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"categories" : {$all : ["cat4", "cat2", "cat1"]}} ); + */ + + $all : function ( qu, value ) { + var operands, result; + + result = false; + if ( sys.isArray( value ) ) { + operands = sys.flatten( qu.operands ); + result = sys.intersection( operands, value ).length === operands.length; + } + return result; + }, + /** + * `$gt` Sees if a field is greater than the value + * `{field: {$gt: value}}` + * @name $gt + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$gt : 24}} ); + */ + + $gt : function ( qu, value ) { + return qu.operands[0] < value; + }, + /** + * `$gte` Sees if a field is greater than or equal to the value + * `{field: {$gte: value}}` + * @name $gte + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$gte : 50}} ); + */ + + $gte : function ( qu, value ) { + return qu.operands[0] <= value; + }, + /** + * `$lt` Sees if a field is less than the value + * `{field: {$lt: value}}` + * @name $lt + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$lt : 24}} ); + */ + + $lt : function ( qu, value ) { + return qu.operands[0] > value; + }, + /** + * `$lte` Sees if a field is less than or equal to the value + * `{field: {$lte: value}}` + * @name $lte + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$lte : 50}} ); + */ + + $lte : function ( qu, value ) { + return qu.operands[0] >= value; + }, + /** + * `$in` Sees if a field has one of the values in the query + * `{field: {$in: [test1, test2, test3,...]}}` + * @name $in + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$in : [24, 28, 60]}} ); + */ + + $in : function ( qu, value ) { + var operands; + + operands = sys.flatten( qu.operands ); + return sys.indexOf( operands, value ) > -1; + }, + /** + * `$nin` Sees if a field has none of the values in the query + * `{field: {$nin: [test1, test2, test3,...]}}` + * @name $nin + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$nin : [24, 28, 60]}} ); + */ + + $nin : function ( qu, value ) { + var operands; + + operands = sys.flatten( qu.operands ); + return sys.indexOf( operands, value ) === -1; + }, + /** + * `$exists` Sees if a field exists. + * `{field: {$exists: true|false}}` + * @name $exists + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"name.middle" : {$exists : true}} ); + */ + + $exists : function ( qu, value ) { + return (sys.isNull( value ) || sys.isUndefined( value )) !== qu.operands[0]; + }, + /** + * Checks equality to a modulus operation on a field + * `{field: {$mod: [divisor, remainder]}}` + * @name $mod + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"age" : {$mod : [2, 0]}} ); + */ + + $mod : function ( qu, value ) { + var operands = sys.flatten( qu.operands ); + if ( operands.length !== 2 ) { + throw new Error( "$mod requires two operands" ); + } + var mod = operands[0]; + var rem = operands[1]; + return value % mod === rem; + }, + /** + * Compares the size of the field/array to the query. This can be used on arrays, strings and objects (where it will count keys) + * `{'field|array`: {$size: value}}` + * @name $size + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {attr : {$size : 3}} ); + */ + + $size : function ( qu, value ) { + return sys.size( value ) === qu.operands[0]; + }, + /** + * Performs a regular expression test againts the field + * `{field: {$regex: re, $options: reOptions}}` + * @name $regex + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {"name.first" : {$regex : "m*", $options : "i"}} ); + */ + + $regex : function ( qu, value ) { + var r = new RegExp( qu.operands[0], qu.options ); + return r.test( value ); + }, + /** + * This is like $all except that it works with an array of objects or value. It checks to see the array matches all + * of the conditions of the query + * `{array: {$elemMatch: {path: value, path: {$operation: value2}}}` + * @name $elemMatch + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {attr : {$elemMatch : [ + * {color : "red", "hand" : "left"} + * ]}} ); + */ + $elemMatch : function ( qu, value ) { + var expression, test, _i, _len; + + if ( sys.isArray( value ) ) { + var _ref = qu.operands; + for ( _i = 0, _len = _ref.length; _i < _len; _i++ ) { + expression = _ref[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + test = execQuery( value, qu.operands, null, true ).arrayResults; + } + return test.length > 0; + }, + /** + * Returns true if all of the conditions of the query are met + * `{$and: [query1, query2, query3]}` + * @name $and + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {$and : [ + * {"name.first" : "Mildred"}, + * {"name.last" : "Graves"} + * ]} ); + */ + + $and : function ( qu, value, record ) { + var isAnd = false; + + sys.each( qu.operands, function ( expr ) { + if ( expr.path ) { + expr.splitPath = expr.splitPath || splitPath( expr.path ); + } + var test = reachin( expr.splitPath, record, expr.operation ); + isAnd = operations[expr.operation]( expr, test, record ); + if ( !isAnd ) { + return false; + } + } ); + + return isAnd; + }, + /** + * Returns true if any of the conditions of the query are met + * `{$or: [query1, query2, query3]}` + * @name $or + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {$or : [ + * "age" : {$in : [24, 28, 60]}}, + * {categories : "cat1"} + * ]} ); + */ + $or : function ( qu, value, record ) { + var isOr = false; + sys.each( qu.operands, function ( expr ) { + if ( expr.path ) { + expr.splitPath = expr.splitPath || splitPath( expr.path ); + } + var test = reachin( expr.splitPath, record, expr.operation ); + isOr = operations[expr.operation]( expr, test, record ); + if ( isOr ) { + return false; + } + } ); + + return isOr; + }, + /** + * Returns true if none of the conditions of the query are met + * `{$nor: [query1, query2, query3]}` + * @name $nor + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {$nor : [ + * {"age" : {$in : [24, 28, 60]}}, + * {categories : "cat1"} + * ]} ); + */ + $nor : function ( qu, value, record ) { + var isOr = false; + sys.each( qu.operands, function ( expr ) { + if ( expr.path ) { + expr.splitPath = expr.splitPath || splitPath( expr.path ); + } + var test = reachin( expr.splitPath, record, expr.operation ); + isOr = operations[expr.operation]( expr, test, record ); + if ( isOr ) { + return false; + } + } ); + + return !isOr; + }, + /** + * Logical NOT on the conditions of the query + * `{$not: [query1, query2, query3]}` + * @name $not + * @memberOf module:documents/probe.queryOperators + * @example + * var probe = require("documents/probe"); + * probe.find( data, {$not : {"age" : {$lt : 24}}} ); + */ + $not : function ( qu, value, record ) { + + var result = false; + sys.each( qu.operands, function ( expr ) { + if ( expr.path ) { + expr.splitPath = expr.splitPath || splitPath( expr.path ); + } + var test = reachin( expr.splitPath, record, expr.operation ); + result = operations[expr.operation]( expr, test, record ); + if ( result ) { + return false; + } + } ); + + return !result; + + } +}; + +/** + Executes a query by traversing a document and evaluating each record + @private + @param {array|object} obj The object to query + @param {object} qu The query to execute + @param {?boolean} shortCircuit When true, the condition that matches the query stops evaluation for that record, otherwise all conditions have to be met + @param {?boolean} stopOnFirst When true all evaluation stops after the first record is found to match the conditons + **/ +function execQuery( obj, qu, shortCircuit, stopOnFirst ) { + var arrayResults = []; + var keyResults = []; + sys.each( obj, function ( record, key ) { + var expr, result, test, _i, _len; + + for ( _i = 0, _len = qu.length; _i < _len; _i++ ) { + expr = qu[_i]; + if ( expr.splitPath ) { + test = reachin( expr.splitPath, record, expr.operation ); + } + result = operations[expr.operation]( expr, test, record ); + if ( result ) { + arrayResults.push( record ); + keyResults.push( key ); + } + if ( !result && shortCircuit ) { + break; + } + } + if ( arrayResults.length > 0 && stopOnFirst ) { + return false; + } + } ); + return { + arrayResults : arrayResults, + keyResults : keyResults + }; +} + +/** + Updates all records in obj that match the query. See {@link module:documents/probe.updateOperators} for the operators that are supported. + @param {object|array} obj The object to update + @param {object} qu The query which will be used to identify the records to updated + @param {object} setDocument The update operator. See {@link module:documents/probe.updateOperators} + */ +exports.update = function ( obj, qu, setDocument ) { + var records = exports.find( obj, qu ); + return sys.each( records, function ( record ) { + return sys.each( setDocument, function ( fields, operator ) { + return sys.each( fields, function ( newValue, path ) { + return pushin( splitPath( path ), record, operator, newValue ); + } ); + } ); + } ); +}; +/** + Find all records that match a query + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @returns {array} The results + **/ +exports.find = function ( obj, qu ) { + var expression, _i, _len; + + var query = parseQueryExpression( qu ); + for ( _i = 0, _len = query.length; _i < _len; _i++ ) { + expression = query[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + return execQuery( obj, query ).arrayResults; +}; +/** + Find all records that match a query and returns the keys for those items. This is similar to {@link module:documents/probe.find} but instead of returning + records, returns the keys. If `obj` is an object it will return the hash key. If 'obj' is an array, it will return the index + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @returns {array} + */ +exports.findKeys = function ( obj, qu ) { + var expression, _i, _len; + + var query = parseQueryExpression( qu ); + for ( _i = 0, _len = query.length; _i < _len; _i++ ) { + expression = query[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + return execQuery( obj, query ).keyResults; +}; + +/** + Returns the first record that matches the query. Aliased as `seek`. + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @returns {object} + */ +exports.findOne = function ( obj, qu ) { + var expression, _i, _len; + + var query = parseQueryExpression( qu ); + for ( _i = 0, _len = query.length; _i < _len; _i++ ) { + expression = query[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + var results = execQuery( obj, query, false, true ).arrayResults; + if ( results.length > 0 ) { + return results[0]; + } else { + return null; + } +}; +exports.seek = exports.findOne; +/** + Returns the first record that matches the query and returns its key or index depending on whether `obj` is an object or array respectively. + Aliased as `seekKey`. + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @returns {object} + */ +exports.findOneKey = function ( obj, qu ) { + var expression, _i, _len; + + var query = parseQueryExpression( qu ); + for ( _i = 0, _len = query.length; _i < _len; _i++ ) { + expression = query[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + var results = execQuery( obj, query, false, true ).keyResults; + if ( results.length > 0 ) { + return results[0]; + } else { + return null; + } +}; +exports.seekKey = exports.findOneKey; + +/** + Remove all items in the object/array that match the query + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @return {object|array} The array or object as appropriate without the records. + **/ +exports.remove = function ( obj, qu ) { + var expression, _i, _len; + + var query = parseQueryExpression( qu ); + for ( _i = 0, _len = query.length; _i < _len; _i++ ) { + expression = query[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + var results = execQuery( obj, query, false, false ).keyResults; + if ( sys.isArray( obj ) ) { + var newArr = []; + sys.each( obj, function ( item, index ) { + if ( sys.indexOf( results, index ) === -1 ) { + return newArr.push( item ); + } + } ); + return newArr; + } else { + sys.each( results, function ( key ) { + return delete obj[key]; + } ); + return obj; + } +}; +/** + Returns true if all items match the query + + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @returns {boolean} + **/ +exports.all = function ( obj, qu ) { + return exports.find( obj, qu ).length === sys.size( obj ); +}; + +/** + Returns true if any of the items match the query + + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @returns {boolean} + **/ +exports.any = function ( obj, qu ) { + var expression, _i, _len; + + var query = parseQueryExpression( qu ); + for ( _i = 0, _len = query.length; _i < _len; _i++ ) { + expression = query[_i]; + if ( expression.path ) { + expression.splitPath = splitPath( expression.path ); + } + } + var results = execQuery( obj, query, true, true ).keyResults; + return results.length > 0; +}; + +/** + Returns the set of unique records that match a query + @param {array|object} obj The object to query + @param {object} qu The query to execute. See {@link module:documents/probe.queryOperators} for the operators you can use. + @return {array} + **/ +exports.unique = function ( obj, qu ) { + var test = exports.find( obj, qu ); + return sys.unique( test, function ( item ) { + return JSON.stringify( item ); + } ); +}; + +/** + This will write the value into a record at the path, creating intervening objects if they don't exist. This does not work as filtered + update and is meant to be used on a single record. It is a nice way of setting a property at an arbitrary depth at will. + + @param {array} path The split path of the element to work with + @param {object} record The record to reach into + @param {string} setter The set operation. See {@link module:documents/probe.updateOperators} for the operators you can use. + @param {object} newValue The value to write to the, or if the operator is $pull, the query of items to look for + */ +exports.set = function ( record, path, setter, newValue ) { + return pushin( splitPath( path ), record, setter, newValue ); +}; + +/** + Reaches into an object and allows you to get at a value deeply nested in an object. This is not a query, but a + straight reach in, useful for event bindings + + @param {array} path The split path of the element to work with + @param {object} record The record to reach into + @return {*} Whatever was found in the record + **/ +exports.get = function ( record, path ) { + return reachin( splitPath( path ), record ); +}; + +/** + Returns true if any of the items match the query. Aliases as `any` + @function + @param {array|object} obj The object to query + @param {object} qu The query to execute + @returns {boolean} + */ +exports.some = exports.any; + +/** + Returns true if all items match the query. Aliases as `all` + @function + @param {array|object} obj The object to query + @param {object} qu The query to execute + @returns {boolean} + */ +exports.every = exports.all; + +var bindables = { + any : exports.any, + all : exports.all, + remove : exports.remove, + seekKey : exports.seekKey, + seek : exports.seek, + findOneKey : exports.findOneKey, + findOne : exports.findOne, + findKeys : exports.findKeys, + find : exports.find, + update : exports.update, + some : exports.some, + every : exports.every, + "get" : exports.get, + "set" : exports.set +}; + +/** + Binds the query and update methods to a new object. When called these + methods can skip the first parameter so that find(object, query) can just be called as find(query) + @param {object|array} obj The object or array to bind to + @return {object} An object with method bindings in place + **/ +exports.proxy = function ( obj ) { + var retVal; + + retVal = {}; + sys.each( bindables, function ( val, key ) { + retVal[key] = sys.bind( val, obj, obj ); + } ); + return retVal; +}; + +/** + Binds the query and update methods to a specific object and adds the methods to that object. When called these + methods can skip the first parameter so that find(object, query) can just be called as object.find(query) + @param {object|array} obj The object or array to bind to + @param {object|array=} collection If the collection is not the same as this but is a property, or even + a whole other object, you specify that here. Otherwise the obj is assumed to be the same as the collecion + **/ +exports.mixin = function ( obj, collection ) { + collection = collection || obj; + return sys.each( bindables, function ( val, key ) { + obj[key] = sys.bind( val, obj, collection ); + } ); +}; + +/** + * These are the supported query operators + * + * @memberOf module:documents/probe + * @name queryOperators + * @class This is not actually a class, but an artifact of the documentation system + */ + +/** + * These are the supported update operators + * + * @memberOf module:documents/probe + * @name updateOperators + * @class This is not actually a class, but an artifact of the documentation system + */ diff --git a/docdash-template/fixtures/documents/schema.js b/docdash-template/fixtures/documents/schema.js new file mode 100644 index 00000000..1d5bdc2c --- /dev/null +++ b/docdash-template/fixtures/documents/schema.js @@ -0,0 +1,292 @@ +"use strict"; +/** + * @fileOverview Enables a schema and validation feature set to your document or other object. + * @module documents/schema + * @requires base + * @requires jjv + * @require lodash + */ +var sys = require( "lodash" ); +var Validator = require( "jjv" ); +var Base = require( "../base" ); +/** + * The validator mixin provides access to the features of the JSON validation system + * @exports documents/schema + * @mixin + */ +var Schema = Base.compose( [Base], /** @lends documents/schema# */{ + constructor : function () { + /** + * The schema that defines the validation rules. This should probably be defined at the prototype for each + * object or model classification. It can be an anonymous schema defined right here, or this can be + * registered schema names to use, or just a single name + * + * @type {object} + * @memberOf documents/schema# + * @name schema + */ + + /** + * If you want to register multiple schemas, use this property instead + * + * @type {object} + * @memberOf documents/schema# + * @name schemas + */ + + /** + * The validation environment + * @private + * @type {jjv} + */ + var env = new Validator(); + + /** + * The default name of the scheman when you use anonymous schemas. You can define this at the prototype for classified + * schemas. The can also + * + * @type {string|function():{string}} + * @memberOf documents/schema# + * @name _defaultSchemaName + */ + this._defaultSchemaName = sys.result( this, "_defaultSchemaName" ) || sys.uniqueId( "schema" ); + + /** + * The options to pass to the validator when it runs + * @type {object|function():{object}} + * @name validationOptions + * @memberOf documents/schema# + */ + this.validationOptions = sys.defaults( {}, sys.result( this, 'validationOptions' ), {checkRequired : true} ); + + /** + * Validate an object against the schema + * @returns {object?} + * @method + * @name validate + * @memberOf documents/schema# + * @param {object=} record The record to validate + * @param {string|object=} schemaName The name of a previously registered schema + * @param {object=} options Options to pass to the validator + * @example + * // This supports these signatures: + * + * instance.validate(record, schemaName, options); + * + * + * instance.validate(); // this, this._defaultSchemaName, this.validationOptions + * instance.validate(record); // record, this._defaultSchemaName, this.validationOptions + * instance.validate(schemaName); //this, schemaName, this.validationOptions + * instance.validate(record, schemaName); //record, schemaName, this.validationOptions + * instance.validate(schemaName, options); //this, schemaName, this.validationOptions + */ + this.validate = function ( record, schemaName, options ) { + if ( arguments.length === 0 ) { + record = this; + schemaName = this._defaultSchemaName; + options = this.validationOptions; + } else { + if ( sys.isString( record ) ) { + schemaName = record; + record = this; + } + if ( sys.isEmpty( options ) ) { + options = this.validationOptions; + } + } + + return env.validate( schemaName, record, options ); + }; + + /** + * Initialize the schema collection by registering the with the handler. You can call this at any time and as often as you like. It will be called once + * by the constructor on any instance schemas + * @method + * @name registerSchemas + * @memberOf documents/schema# + * @param {hash} schemas A hash of schemas where the key is the name of the schema + */ + this.registerSchemas = function ( schemas ) { + var schema = sys.result( this, "schema" ); + var schemas = schemas || sys.result( this, "schemas" ); + if ( !sys.isEmpty( schema ) ) { + env.addSchema( this._defaultSchemaName, schema ); + } + if ( !sys.isEmpty( schemas ) ) { + sys.each( schemas, function ( val, key ) { + env.addSchema( val, key ); + } ); + } + }; + + /** + * Extracts only the elements of the object that are defined in the schema + * @memberOf documents/schema# + * @name extract + * @param {object=} record The record to extract from + * @param {string=} schema The name of the schema to attach + * @method + */ + this.extract = function ( record, schema ) { + if ( arguments.length === 0 ) { + record = this; + schema = this._defaultSchemaName; + } + if ( sys.isString( record ) ) { + schema = record; + record = this; + } + }; + + /** + * Create a type to be used in your schemas to define new validators + * @memberOf documents/schema# + * @name addType + * @method + * @param {string} name The name of the type + * @param {function(object)} operation What to do with the type. + * @param {object} operation.value The value to validation + * @returns {boolean} + */ + this.addType = env.addType; + + /** + * It is also possible to add support for additional string formats through the addFormat function. + * @memberOf documents/schema# + * @name addFormat + * @method + * @param {string} name The name of the formatter + * @param {function(object)} formatter How to format it + * @param {object} formatter.value The value to format + * @returns {boolean} + */ + this.addFormat = env.addFormat; + + /** + * It is possible to add support for custom checks (i.e., minItems, maxItems, minLength, maxLength, etc.) through the addCheck function + * @memberOf documents/schema# + * @name addCheck + * @method + * @param {string} name The name of the check + * @param {function(...object)} formatter Perform the check + * @param {object} formatter.value The value to check followed by any parameters from the schema + * @returns {boolean} + */ + this.addCheck = env.addCheck; + + /** + * Custom coercion rules + * + * @memberOf documents/schema# + * @name addTypeCoercion + * @method + * @param {string} name The name of the coercion + * @param {function(object)} coercer Perform the coercion + * @param {object} coercer.value The value to coerce + * @returns {boolean} + */ + this.addTypeCoercion = env.addTypeCoercion; + + /** + * Get a registered schema by name + * @param {string=} schemaName + * @returns {object?} + * @memberOf documents/schema# + * @name getSchema + * @method + */ + this.getSchema = function ( schemaName ) { + if ( sys.isEmpty( schemaName ) || !sys.isString() ) { + schemaName = this._defaultSchemaName; + } + return env.schema[schemaName]; + } + }, + /** + * This method will create a new object that contains only the fields and no methods or other artifacts. This is useful + * for creating objects to pass over the wire or save in a table. This is not deeply copied, so changes made to the + * extracted object will be represented in this class for reference objects. + * + * @param {string=} schema The schema name to use + * @param {object=} src The object to extract fields from + * @return {object} Data-only version of the class instance. + */ + extract : function ( schemaName, src ) { + if ( sys.isObject( schemaName ) ) { + src = schema; + schemaName = this._defaultSchemaName; + } + + if ( sys.isEmpty( src ) ) { + src = this; + } + + if ( sys.isFunction( src.toJSON ) ) { + src = src.toJSON(); + } + var schema = this.getSchema( schemaName ) || {}; + var newobj = {}; + sys.each( schema.properties, function ( prop, propname ) { + if ( prop.properties && !sys.isUndefined( src[ propname ] ) ) { + newobj[ propname ] = this.extract( prop, src[propname] ); + } else if ( !sys.isUndefined( src[ propname ] ) ) { + newobj[ propname ] = src[ propname ]; + } + }, this ); + + return newobj; + }, + /** + * Builds a default document based on the schema. What this does is create a document from schema and for each property + * that has a default value or is required, the resultant object will contain that property. It is useful for extending + * values from some source that may be incomplete, like options or some such. + * @param {json-schema} schema A schema to use to create the default document + * @returns {object?} + * @name defaultDoc + * @memberOf documents/schema# + * @method + */ + defaultDoc : function ( schemaName ) { + if ( sys.isEmpty( schemaName ) ) { + schemaName = this._defaultSchemaName; + } + var newdoc = {}; + var schema; + + if ( sys.isObject( schemaName ) ) { + schema = schemaName; + } else { + schema = this.getSchema( schemaName ) || {}; + } + sys.each( schema.properties, function ( val, key ) { + + var def = val[ "default" ]; // keyword and all that + if ( val.type === "object" && !sys.isEmpty( val.properties ) ) { + newdoc[ key ] = this.defaultDoc( val ); + } else { + if ( sys.isFunction( def ) || sys.isBoolean( def ) || sys.isNumber( def ) || !sys.isEmpty( def ) ) { + + if ( sys.isFunction( def ) ) { + newdoc[ key ] = def( schema ); + } else { + newdoc[ key ] = def; + } + } else if ( val.required ) { + if ( val.type === 'string' ) { + newdoc[ key ] = null; + } else if ( val.type === 'object' ) { + newdoc[ key ] = {}; + } else if ( val.type === 'array' ) { + newdoc[ key ] = []; + } else { + newdoc[ key ] = null; + } + } + } + }, this ); + + return newdoc; + } +} ); + +module.exports = Schema; diff --git a/docdash-template/fixtures/fixtures.conf.json b/docdash-template/fixtures/fixtures.conf.json new file mode 100644 index 00000000..4fc1afd3 --- /dev/null +++ b/docdash-template/fixtures/fixtures.conf.json @@ -0,0 +1,63 @@ +{ + "tags": { + "allowUnknownTags": true + }, + "source": { + "include": [ + "fixtures/", + "./README.md" + ] + }, + "plugins": ["plugins/markdown"], + "opts": { + "encoding": "utf8", + "template": "./", + "destination": "./fixtures-doc/", + "recurse": true, + "verbose": true + }, + "markdown": { + "parser": "gfm", + "hardwrap": true, + "idInHeadings": true + }, + "templates": { + "cleverLinks": false, + "monospaceLinks": false, + "default": { + "outputSourceFiles": true, + "includeDate": false, + "useLongnameInNav": true + } + }, + "docdash": { + "static": true, + "sort": true, + "disqus": "", + "openGraph": { + "title": "Docdash", + "type": "website", + "image": "https://cloud.githubusercontent.com/assets/447956/13398144/4dde7f36-defd-11e5-8909-1a9013302cb9.png", + "site_name": "Docdash", + "url": "http://clenemt.github.io/docdash/" + }, + "meta": { + "title": "Docdash", + "description": "A clean, responsive documentation template theme for JSDoc 3.", + "keyword": "jsdoc, docdash" + }, + "search": true, + "collapse": true, + "typedefs": true, + "removeQuotes": "none", + "scripts": [], + "menu":{ + "Github repo":{ + "href":"https://github.com/clenemt/docdash", + "target":"_blank", + "class":"menu-item", + "id":"repository" + } + } + } +} diff --git a/docdash-template/fixtures/generators/generator.js b/docdash-template/fixtures/generators/generator.js new file mode 100644 index 00000000..11f210f4 --- /dev/null +++ b/docdash-template/fixtures/generators/generator.js @@ -0,0 +1,18 @@ +/** + * Generate numbers in the Fibonacci sequence. + * + * @generator + * @function fibonacci + * @yields {number} The next number in the Fibonacci sequence. + */ + + function *fibonacci(n) { + const infinite = !n && n !== 0; + let current = 0; + let next = 1; + + while (infinite || n--) { + yield current; + [current, next] = [next, current + next]; + } +} \ No newline at end of file diff --git a/docdash-template/fixtures/mixins/bussable.js b/docdash-template/fixtures/mixins/bussable.js new file mode 100644 index 00000000..f40a6458 --- /dev/null +++ b/docdash-template/fixtures/mixins/bussable.js @@ -0,0 +1,98 @@ +"use strict"; +/** + * @fileOverview Provides easy access to the system bus and provides some helper methods for doing so + * @module mixins/bussable + * @requires postal + * @requires lodash + * @requires base + */ +var bus = require( "postal" ); +var Base = require( "../base" ); +var sys = require( "lodash" ); + +/** + * @classDesc Provides easy access to the system bus and provides some helper methods for doing so + * @exports mixins/bussable + * @mixin + */ +var Bussable = Base.compose( [Base], /** @lends mixins/bussable# */{ + declaredClass : "mixins/Bussable", + constructor : function () { + /** + * The list of subscriptions maintained by the mixin + * @type {Array} + * @memberof mixins/bussable# + * @name _subscriptions + * @private + */ + this._subscriptions = {}; + + this.log.trace( "Bussable constructor" ); + }, + + /** + * Subscribe to an event + * @param {string} channel The channel to subscribe to + * @param {string} topic The topic to subscribe to + * @param {callback} callback What to do when you get the event + * @returns {object} The subscription definition + */ + subscribe : function ( channel, topic, callback ) { + this.log.trace( "Bussable subscribe" ); + var sub = bus.subscribe( {channel : channel, topic : topic, callback : callback} ); + this.subscriptions[channel + "." + topic] = sub; + return sub; + }, + + /** + * Subscribe to an event once + * @param {string} channel The channel to subscribe to + * @param {string} topic The topic to subscribe to + * @param {callback} callback What to do when you get the event + * @returns {object} The subscription definition + */ + once : function ( channel, topic, callback ) { + this.log.trace( "Bussable once" ); + var sub = this.subscribe( channel, topic, callback ); + this.subscriptions[channel + "." + topic] = sub; + sub.disposeAfter( 1 ); + return sub; + }, + + /** + * Publish an event on the system bus + * @param {string} channel The channel to publish to + * @param {string} topic The topic to publish to + * @param {object=} options What to pass to the event + */ + publish : function ( channel, topic, options ) { + this.log.trace( "Bussable publish" ); + bus.publish( {channel : channel, topic : topic, data : options} ); + }, + + /** + * Get a subscription definition + * + * @param {string} channel + * @param {string} topic + * @returns {object=} The subscription definition + */ + getSubscription : function ( channel, topic ) { + this.log.trace( "Bussable getSubscription" ); + return this.subscriptions[channel + "." + topic]; + }, + + /** + * Gets rid of all subscriptions for this object. + * @private + */ + destroy : function () { + this.log.trace( "Bussable destroy" ); + + sys.each( this.subscriptions, function ( sub ) { + sub.unsubscribe(); + } ); + } +} ); + +module.exports = Bussable; diff --git a/docdash-template/fixtures/mixins/signalable.js b/docdash-template/fixtures/mixins/signalable.js new file mode 100644 index 00000000..e194fa2d --- /dev/null +++ b/docdash-template/fixtures/mixins/signalable.js @@ -0,0 +1,245 @@ +"use strict"; +/** + * @fileOverview Add the ability to fire signals on your objects. Signals are events, but hard coded into the object + * and don't rely on strings like other events and `eventables` + * @module mixins/signalable + * @requires base + * @requires signals + * @requires base/logger + */ + +var Base = require( "../base" ); +var signals = require( 'signals' ); +var format = require( "../strings/format" ); +var sys = require( "lodash" ); + +/** + * @typedef + * @property {boolean=} memorize If Signal should keep record of previously dispatched parameters and automatically execute listener. Defaults to `false` + * @property {array=} params Default parameters passed to listener during `Signal.raise`/`Signal.fire`/`Signal.trigger` and SignalBinding.execute. (curried parameters). Defaults to `null` + * @property {object=} context When provided the signal will be raised in the context of this object. Defaults to `this` - the signal host + * @name SignalOptions + * @memberOf module:mixins/signalable + * @example + * + * signals:{ + * opened: null, + * twisted: { memorize:true }, + * applied: { memorize: false, params:[one, two] } + * } + * + * // Setting the context initially can be a hassle, so this also supports a function that returns a hash + * + * signals: function(){ + * return { + * opened: null, + * twisted: { memorize:true }, + * applied: { memorize: false, params:[one, two] }, + * reversed: {context: someOtherRuntimeObject} + * }; + * } + * + */ + +/** + * @classDesc A signal that can be raised on an object. When you deploy the `Signalable` mixin, it + * creates instances of these for you. + * + * @constructor + * @param {?object} host If hosted, you can identify the host here. + * @param {?string} name The name of the signal + * @type module:mixins/signalable.SignalOptions + */ +var Signal = Base.compose( [Base, signals.Signal], /** @lends module:mixins/signalable~Signal# */{ + declaredClass : "mixins/Signal", + + constructor : function ( host, name, options ) { + options = options || {}; + this.memorize = options.memorize === true; + this.host = host; + this.trigger = this.fire = this.raise = this.dispatch; + this.name = name || sys.uniqueId( "signal" ); + this.params = options.params; + this.defaultContext = options.context; + }, + + /** + * Cleans up + * @private + */ + destroy : function () { + this.removeAll(); + this.dispose(); + this.host = null; + }, + + /** + * Ties a listener to a signal. + * @param {function} listener The function to call when the signal is raised + * @param {?object} listenerContext A context to set for the listener. The event host may set a default for this value, but you may override that here. + * @param {?number} priority A priority for the listener. + * @returns {SignalBinding} + */ + on : function ( listener, listenerContext, priority ) { + if ( sys.isNumber( listenerContext ) ) { + priority = listenerContext; + listenerContext = null; + } + listenerContext = listenerContext || this.defaultContext || this.host; + var binding = this.add( listener, listenerContext, priority ); + if ( this.options.params ) { + binding.params = this.arams; + } + return binding; + }, + /** + * Ties a listener to for a signal for one execution. + * @param {function} listener The function to call when the signal is raised + * @param {?object} listenerContext A context to set for the listener. The event host may set a default for this value, but you may override that here. + * @param {?number} priority A priority for the listener. + * @returns {SignalBinding} + */ + once : function ( listener, listenerContext, priority ) { + if ( sys.isNumber( listenerContext ) ) { + priority = listenerContext; + listenerContext = null; + } + listenerContext = listenerContext || this.defaultContext || this.host; + var binding = this.addOnce( listener, listenerContext, priority ); + if ( this.options.params ) { + binding.params = this.params; + } + return binding; + }, + /** + * Unbinds a listener to a signal. + * @param {function} listener The function to unbind + * @param {?object} listenerContext The context that was bound + * @returns {function} + */ + off : function ( listener, listenerContext ) { + listenerContext = listenerContext || this.host; + return this.remove( listener, listenerContext ); + }, + /** + * Check if listener was attached to Signal. + * @param {function} listener The function to check + * @param {?object} listenerContext The context that was bound + * @returns {boolean} + */ + has : function ( listener, listenerContext ) { + listenerContext = listenerContext || this.defaultContext || this.host; + return this.remove( listener, listenerContext ); + }, + /** + * Strings! + */ + toString : function () { + return format( "{0}\nname:{1}\nlisteners:{2}", + this.declaredClass, + this.name, + this.getNumListeners() + ); + } + +} ); + +/** + * @classDesc Make an object capable of handling a signal. Or many signals. + * @exports mixins/signalable + * @mixin + * @extends base + */ +var Signalable = Base.compose( [Base], /** @lends mixins/signalable# */{ + declaredClass : "mixins/Signalable", + + constructor : function () { + this.autoLoadSignals = this.autoLoadSignals || true; + if ( this.autoLoadSignals === true ) { + this._loadSignals(); + } + }, + /** + * When you make a change to the signals hash after loading, then you can make it reload + */ + refreshSignals : function () { + this._loadSignals(); + }, + + /** + * Interprets the `signals` hash and instantiates it + * @private + */ + _loadSignals : function () { + var signals = this.signals || {}; + sys.each( signals, function ( value, key ) { + var opts = {}; + if ( !sys.isEmpty( value ) ) { + if ( sys.isBoolean( value.memorize ) ) { + opts.memorize = value.memorize; + } + if ( sys.isBoolean( value.params ) ) { + opts.params = value.params; + } + if ( !sys.isEmpty( value.context ) ) { + opts.context = value.context; + } + } + this._addSignal( key, opts ); + } ); + }, + /** + * Creates a single signal + * @param {string} name The name of the signal + * @param {module:mixins/signalable~SignalOptions} options The options the signal expects + * @private + */ + _addSignal : function ( name, options ) { + if ( sys.isEmpty( this[name] ) ) { + this[name] = new Signal( this, name, options ); + } + }, + + /** + * Add a signal to an object. If any members of the hash already exist in `this.signals`, they will be overwritten. + * @param {module:mixins/signalable.SignalOptions} signals + * @private + */ + _addSignals : function ( signals ) { + signals = signals || {}; + if ( this.options ) {signals = sys.extend( {}, sys.result( this, 'signals' ), signals );} + this.signals = signals; + }, + /** + * Clean up + * @private + */ + destroy : function () { + sys.each( sys.keys( this ), function ( key ) { + if ( this[key] instanceof Signal || this[key] instanceof signals.Signal ) { + this[key].close(); + } + }, this ); + } +} ); + +module.exports = Signalable; +alable.Signal = Signal; +Signalable.mixin = Base.mixin; + +/** + * When true, the class will load the `signals` hash and create the signal definitions during construction + * @memberOf mixins/signalable# + * @name autoLoadSignals + * @type boolean + */ + + +/** + * A hash of signals to create automatically. Each definition consists of a name for the signal as the key + * and then a hash of options (nullable) for each signal + * @type {hash|function():hash} + * @memberOf mixins/signalable# + * @name signals + * @type module:mixins/signalable.SignalOptions + */ diff --git a/docdash-template/fixtures/strings/format.js b/docdash-template/fixtures/strings/format.js new file mode 100644 index 00000000..6098fba7 --- /dev/null +++ b/docdash-template/fixtures/strings/format.js @@ -0,0 +1,32 @@ +"use strict"; +/** + * @fileOverview String helper methods + * + * @module strings/format + */ + +/** + * Format a string quickly and easily using .net style format strings + * @param {string} format A string format like "Hello {0}, now take off your {1}!" + * @param {...?} args One argument per `{}` in the string, positionally replaced + * @returns {string} + * + * @example + * var strings = require("papyrus/strings"); + * var s = strings.format("Hello {0}", "Madame Vastra"); + * // s = "Hello Madame Vastra" + * + * @example {@lang xml} + * + * <%= strings.format("Hello {0}", "Madame Vastra") %> + * + */ +module.exports = function ( format ) { + var args = Array.prototype.slice.call( arguments, 1 ); + return format.replace( /{(\d+)}/g, function ( match, number ) { + return typeof args[number] != 'undefined' + ? args[number] + : match + ; + } ); +}; diff --git a/docdash-template/fixtures/tutorials/Brush Teeth.md b/docdash-template/fixtures/tutorials/Brush Teeth.md new file mode 100644 index 00000000..db634638 --- /dev/null +++ b/docdash-template/fixtures/tutorials/Brush Teeth.md @@ -0,0 +1,26 @@ +#Lorem ipsum dolor sit amet + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra, tellus et fermentum tincidunt, massa ligula dignissim augue, ut aliquam tortor odio in odio. In faucibus metus metus. Curabitur est mi, fermentum lacinia tincidunt vitae, mattis sit amet neque. Quisque diam nisl, accumsan ac porta tincidunt, iaculis facilisis ipsum. Nulla facilisi. Aenean a metus tortor. Pellentesque congue, mauris vitae viverra varius, elit nunc dictum nisl, rhoncus ultrices nulla sapien at leo. Duis ultricies porttitor diam. Nulla facilisi. Nullam elementum, lorem eu imperdiet laoreet, est turpis sollicitudin velit, in porttitor justo dolor vel urna. Mauris in ante magna. Curabitur vitae lacus in magna mollis commodo. + +| Fusce lacinia | mauris ac aliquam | consequat | lacus urna feugiat erat | id viverra mi mi sit amet tortor | +|---------------|-------------------|-------------|-------------------------------|---------------------------------------| +| Etiam ac | 1 | 3 | 4.5 | 6.78910 | +| Pellentesque e| 2 | 2 | 3 | 4 | + +neque lacus, quis posuere orci. Fusce molestie blandit velit, sit amet dictum eros pharetra vitae. In erat urna, condimentum ac feugiat id, rutrum et nisi. Cras ac velit lorem. Nulla facilisi. Maecenas dignissim nulla in turpis tempus sed rhoncus augue dapibus. Nulla feugiat, urna non sagittis laoreet, dolor metus rhoncus justo, sed semper ante lacus eget quam. Sed ac ligula magna. Sed tincidunt pulvinar neque in porta. Nullam quis lacus orci. Pellentesque ornare viverra lacus, id aliquam magna venenatis a. + +Sed id tristique lorem. Ut sodales turpis nec mauris gravida interdum. Cras pellentesque, purus at suscipit euismod, elit nunc cursus nisi, ut venenatis metus sapien id velit. Sed lectus orci, pharetra non pulvinar vel, ullamcorper id lorem. Donec vulputate tincidunt ipsum, ut lacinia tortor sollicitudin id. Nunc nec nibh ut felis venenatis egestas. Proin risus mauris, eleifend eget interdum in, venenatis sed velit. Praesent sodales elit ut odio viverra posuere. Donec sapien lorem, molestie in egestas eget, vulputate sed orci. Aenean elit sapien, pellentesque vitae tempor sit amet, sagittis et ligula. Mauris aliquam sapien sit amet lacus ultrices rutrum. Curabitur nec dolor sed elit varius dignissim a a lacus. Aliquam ac convallis enim. + +Suspendisse orci massa, hendrerit sagittis lacinia consectetur, sagittis vitae purus. Aliquam id eros diam, eget elementum turpis. Nullam tellus magna, mollis in molestie id, venenatis rhoncus est. Proin id diam justo. Nunc tempus gravida justo at lobortis. Nam vitae venenatis nisi. Donec vel odio massa. Quisque interdum metus sit amet est iaculis tincidunt. Donec bibendum blandit purus, id semper orci aliquam quis. Nam tincidunt dolor eu felis ultricies tempor. Nulla non consectetur erat. + +Nunc faucibus lacus eget odio ultricies nec ullamcorper risus pharetra. Nunc nec consequat urna. Curabitur condimentum ante vitae erat tristique vitae gravida quam dapibus. Cras ac justo dui, at faucibus urna. Nunc tristique, velit id feugiat fermentum, dolor enim egestas erat, at vestibulum ante ipsum vel orci. Duis quis ante id justo vehicula eleifend sed et urna. Sed sapien tortor, rutrum id ultrices eu, tincidunt tincidunt mi. Etiam blandit, neque eget interdum dignissim, lacus ante facilisis dolor, non viverra dui lorem vitae nibh. Morbi volutpat augue eget nulla luctus eu aliquam sem facilisis. Pellentesque sollicitudin commodo dolor sit amet vestibulum. Nam dictum posuere quam, in tincidunt erat rutrum eu. + +Etiam nec turpis purus, at lacinia sem. In commodo lacinia euismod. Curabitur tincidunt congue leo, eget iaculis orci volutpat pharetra. Fusce dignissim lacus lacus. Integer consectetur lacus rutrum risus malesuada at consectetur erat rutrum. Sed magna ipsum, fringilla eget auctor non, fringilla nec massa. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum nec tortor id nisi luctus aliquam. Maecenas cursus tincidunt ornare. Nulla a vestibulum odio. Mauris malesuada commodo justo quis mattis. Suspendisse mauris ligula, placerat at egestas in, tincidunt quis nibh. Aliquam ullamcorper elit at augue cursus quis pellentesque purus viverra. + +Nulla ultricies justo ac nisi consectetur posuere. Donec ornare pharetra erat, nec facilisis dui cursus quis. Quisque porttitor porttitor orci, sed facilisis urna facilisis sed. Sed tincidunt adipiscing turpis et hendrerit. Cras posuere orci ut mauris ullamcorper vitae laoreet nisi luctus. In rutrum tristique augue. Nam eleifend dignissim dui. + +Donec viverra egestas tellus non viverra. Aenean est ante, egestas sed scelerisque quis, aliquet sed lacus. Praesent non mauris neque, et adipiscing ante. Vestibulum quis quam vitae ipsum aliquet blandit. Vivamus condimentum euismod orci, in tincidunt justo rutrum faucibus. Phasellus nec lorem arcu. Donec tortor dui, facilisis in rutrum sit amet, pulvinar vitae lacus. Nam sodales sem eu nunc scelerisque vitae ullamcorper dolor facilisis. Duis imperdiet nisi in magna tempor convallis. Fusce at metus augue. Quisque dictum tempus mauris, in mattis ligula dignissim ut. + +Proin sodales, mi at tincidunt ornare, mi dui sagittis velit, sed dictum risus orci eu erat. Sed nunc leo, congue sed rutrum eget, lobortis ac lectus. Etiam non arcu nulla. Vestibulum rutrum dolor pulvinar lorem posuere blandit. Sed quis sapien dui. Nunc sagittis erat commodo quam porta cursus in non erat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin a molestie neque. Aliquam iaculis lacus sed neque hendrerit at dignissim ligula imperdiet. Suspendisse venenatis, lorem at luctus scelerisque, sem purus pellentesque sapien, vitae ornare ipsum quam nec dui. Mauris neque est, interdum nec pulvinar eget, dapibus eleifend tellus. Fusce non lorem tortor. Nullam eget nunc quis felis aliquam consectetur. Aliquam tristique, turpis in feugiat blandit, lectus erat condimentum tortor, non egestas nisl sapien eget nibh. + +Aliquam elit turpis, faucibus et porta et, egestas nec nibh. Sed nisl est, pharetra a eleifend a, pretium ac eros. Sed leo eros, pulvinar vel faucibus dictum, aliquet ut quam. Maecenas et felis non ligula fringilla pretium fringilla sit amet ante. Nam varius imperdiet interdum. Ut non metus mauris, vel volutpat lorem. Nullam sagittis est quis lacus feugiat fringilla. Quisque orci lorem, semper ac accumsan vitae, blandit quis velit. Proin luctus sodales ultrices. Fusce mauris erat, facilisis ut consectetur at, fringilla feugiat orci. Aliquam a nisi a neque interdum suscipit id eget purus. Pellentesque tincidunt justo ut urna posuere non molestie quam auctor. diff --git a/docdash-template/fixtures/tutorials/Drive Car.md b/docdash-template/fixtures/tutorials/Drive Car.md new file mode 100644 index 00000000..a1b0eb10 --- /dev/null +++ b/docdash-template/fixtures/tutorials/Drive Car.md @@ -0,0 +1,10 @@ +#Lorem ipsum dolor sit amet + +Curabitur est mi, fermentum lacinia tincidunt vitae, mattis sit amet neque. Quisque diam nisl, accumsan ac porta tincidunt, iaculis facilisis ipsum. Nulla facilisi. Aenean a metus tortor. Pellentesque congue, mauris vitae viverra varius, elit nunc dictum nisl, rhoncus ultrices nulla sapien at leo. Duis ultricies porttitor diam. Nulla facilisi. Nullam elementum, lorem eu imperdiet laoreet, est turpis sollicitudin velit, in porttitor justo dolor vel urna. Mauris in ante magna. Curabitur vitae lacus in magna mollis commodo. + +##Fusce lacinia, mauris ac aliquam consequat +Fusce molestie blandit velit, sit amet dictum eros pharetra vitae. In erat urna, condimentum ac feugiat id, rutrum et nisi. Cras ac velit lorem. Nulla facilisi. Maecenas dignissim nulla in turpis tempus sed rhoncus augue dapibus. Nulla feugiat, urna non sagittis laoreet, dolor metus rhoncus justo, sed semper ante lacus eget quam. Sed ac ligula magna. Sed tincidunt pulvinar neque in porta. Nullam quis lacus orci. Pellentesque ornare viverra lacus, id aliquam magna venenatis a. + +Sed id tristique lorem. Ut sodales turpis nec mauris gravida interdum. Cras pellentesque, purus at suscipit euismod, elit nunc cursus nisi, ut venenatis metus sapien id velit. Sed lectus orci, pharetra non pulvinar vel, ullamcorper id lorem. Donec vulputate tincidunt ipsum, ut lacinia tortor sollicitudin id. Nunc nec nibh ut felis venenatis egestas. Proin risus mauris, eleifend eget interdum in, venenatis sed velit. Praesent sodales elit ut odio viverra posuere. Donec sapien lorem, molestie in egestas eget, vulputate sed orci. Aenean elit sapien, pellentesque vitae tempor sit amet, sagittis et ligula. Mauris aliquam sapien sit amet lacus ultrices rutrum. Curabitur nec dolor sed elit varius dignissim a a lacus. Aliquam ac convallis enim. + +Suspendisse orci massa, hendrerit sagittis lacinia consectetur, sagittis vitae purus. Aliquam id eros diam, eget elementum turpis. Nullam tellus magna, mollis in molestie id, venenatis rhoncus est. Proin id diam justo. Nunc tempus gravida justo at lobortis. Nam vitae venenatis nisi. Donec vel odio massa. Quisque interdum metus sit amet est iaculis tincidunt. Donec bibendum blandit purus, id semper orci aliquam quis. Nam tincidunt dolor eu felis ultricies tempor. Nulla non consectetur erat. diff --git a/docdash-template/fixtures/tutorials/Fence Test.md b/docdash-template/fixtures/tutorials/Fence Test.md new file mode 100644 index 00000000..c1893a7b --- /dev/null +++ b/docdash-template/fixtures/tutorials/Fence Test.md @@ -0,0 +1,36 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non libero tristique, interdum quam in, fermentum massa. Aenean vestibulum velit eu massa faucibus accumsan. Aenean tempus quam ornare ligula gravida adipiscing. Suspendisse vestibulum diam quis quam lacinia convallis. Nunc rhoncus a elit ut dictum. Maecenas porta mi et risus convallis commodo. In hac habitasse platea dictumst. Morbi placerat sem nec eleifend hendrerit. Donec hendrerit pulvinar tristique. Pellentesque at nunc blandit, fringilla elit nec, dignissim arcu. Quisque sit amet enim urna. Nunc adipiscing lacinia justo. Pellentesque euismod nisi id elit auctor porttitor. Phasellus rutrum viverra felis, ac cursus ante vulputate ut. Donec laoreet felis ac risus vulputate sodales. + +Mauris sit amet risus non ligula lacinia iaculis. Sed ornare tellus velit, vel elementum quam porttitor tempus. Duis vestibulum augue eu diam malesuada auctor. Maecenas dignissim odio ut elit fermentum, id mollis leo mattis. Phasellus posuere augue sed interdum vestibulum. Etiam ac pharetra est. Integer tortor ligula, pharetra ac nisi nec, faucibus laoreet dolor. Nunc vehicula, enim et cursus tincidunt, nulla purus mollis urna, vel ultricies nisl mi a risus. Vestibulum sed urna sodales, pretium nisi sed, pretium sapien. Vivamus et massa tincidunt, semper nibh nec, eleifend urna. Integer auctor, eros at pharetra blandit, erat nibh mattis turpis, rhoncus elementum nisi mi vitae purus. + +Quisque elementum sapien id neque volutpat cursus non mattis velit. + + +``` +$mod : function ( qu, value ) { + var operands = sys.flatten( qu.operands ); + if ( operands.length !== 2 ) { + throw new Error( "$mod requires two operands" ); + } + var mod = operands[0]; + var rem = operands[1]; + return value % mod === rem; + }, + +``` + + +``` +{@lang bash} +#!/bin/bash +echo Please, enter your firstname and lastname +read FN LN +echo "Hi! $LN, $FN !" +``` + +```bash +#!/bin/bash +echo Please, enter your firstname and lastname +read FN LN +echo "Hi! $LN, $FN !" +``` + diff --git a/docdash-template/fixtures/utils/logger.js b/docdash-template/fixtures/utils/logger.js new file mode 100644 index 00000000..8531da78 --- /dev/null +++ b/docdash-template/fixtures/utils/logger.js @@ -0,0 +1,89 @@ +"use strict"; +/** + * @fileOverview The logging system for papyrus is based on [http://pimterry.github.io/loglevel/](loglevel) and slightly decorated + * @module utils/logger + * @requires dcl + * @requires loglevel + */ + +var dcl = require( "dcl" ); +var log = require( 'loglevel' ); + +/** + * A logger class that you can mix into your classes to handle logging settings and state at an object level. + * See {@link utils/logger} for the members of this class + * + * @exports utils/logger.Logger + * @class + * @see utils/logger + */ +var Logger = dcl( null, /** @lends utils/logger.Logger# */{ + declaredClass : "utils/Logger", + + /** + * Turn off all logging. If you log something, it will not error, but will not do anything either + * and the cycles are minimal. + * + */ + silent : function () { + log.disableAll(); + }, + /** + * Turns on all logging levels + * + */ + all : function () { + log.enableAll(); + }, + /** + * Sets the logging level to one of `trace`, `debug`, `info`, `warn`, `error`. + * @param {string} lvl The level to set it to. Can be one of `trace`, `debug`, `info`, `warn`, `error`. + * + */ + level : function ( lvl ) { + if ( lvl.toLowerCase() === "none" ) { + log.disableAll(); + } else { + log.setLevel( lvl ); + } + }, + /** + * Log a `trace` call + * @method + * @param {string} The value to log + */ + trace : log.trace, + /** + * Log a `debug` call + * @method + * @param {string} The value to log + */ + debug : log.debug, + /** + * Log a `info` call + * @method + * @param {string} The value to log + */ + info : log.info, + /** + * Log a `warn` call + * @method + * @param {string} The value to log + */ + warn : log.warn, + /** + * Log a `error` call + * @method + * @param {string} The value to log + */ + error : log.error +} ); + +module.exports = new Logger(); +/** + * The system global, cross-platform logger + * @name utils/logger + * @static + * @type {utils/logger.Logger} + */ +module.exports.Logger = Logger; diff --git a/docdash-template/generateDocs.sh b/docdash-template/generateDocs.sh new file mode 100644 index 00000000..5831c8e8 --- /dev/null +++ b/docdash-template/generateDocs.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +#Original source https://gist.github.com/vidavidorra/548ffbcdae99d752da02 + +if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_REPO_SLUG" == "clenemt/docdash" ]; then + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +echo 'Setting up the script...' +# Exit with nonzero exit code if anything fails +set -e + +# Get the current gh-pages branch +git clone -b gh-pages http://github.com/$TRAVIS_REPO_SLUG repo +cd repo + +##### Configure git. +# Set the push default to simple i.e. push only the current branch. +git config --global push.default simple +# Pretend to be an user called Travis CI. +git config user.name "Travis CI" +git config user.email "travis@travis-ci.org" + +# Remove everything currently in the gh-pages branch. +# GitHub is smart enough to know which files have changed and which files have +# stayed the same and will only update the changed files. So the gh-pages branch +# can be safely cleaned, and it is sure that everything pushed later is the new +# documentation. +rm -rf * + +# Need to create a .nojekyll file to allow filenames starting with an underscore +# to be seen on the gh-pages site. Therefore creating an empty .nojekyll file. +echo "" > .nojekyll + +################################################################################ +##### Generate JSDOC documents. ##### +echo 'Copying generated JSDoc code documentation...' +cp -R ../fixtures-doc/* ./ ; + +################################################################################ +##### Upload the documentation to the gh-pages branch of the repository. ##### +# Only upload if JSDoc successfully created the documentation. +# Check this by verifying that the file index.html exists +if [ -f "index.html" ]; then + + echo 'Uploading documentation to the gh-pages branch...' + # Add everything in this directory to the + # gh-pages branch. + # GitHub is smart enough to know which files have changed and which files have + # stayed the same and will only update the changed files. + git add --all + + # Commit the added files with a title and description containing the Travis CI + # build number and the GitHub commit reference that issued this build. + git commit -m "Deploy code docs to GitHub Pages Travis build: ${TRAVIS_BUILD_NUMBER}" -m "Commit: ${TRAVIS_COMMIT}" + + # Force push to the remote gh-pages branch. + # The ouput is redirected to /dev/null to hide any sensitive credential data + # that might otherwise be exposed. + git push --force "https://${GH_REPO_TOKEN}@github.com/${TRAVIS_REPO_SLUG}" > /dev/null 2>&1 +else + echo '' >&2 + echo 'Warning: No documentation (html) files have been found!' >&2 + echo 'Warning: Not going to push the documentation to GitHub!' >&2 + exit 1 +fi + +fi \ No newline at end of file diff --git a/docdash-template/package.json b/docdash-template/package.json new file mode 100644 index 00000000..77bdf4f6 --- /dev/null +++ b/docdash-template/package.json @@ -0,0 +1,31 @@ +{ + "name": "docdash", + "version": "1.2.0", + "description": "A clean, responsive documentation template theme for JSDoc 3 inspired by lodash and minami", + "main": "publish.js", + "scripts": { + "test": "jsdoc -c fixtures/fixtures.conf.json", + "sync": "browser-sync start -s ../fixtures-doc -f ../fixtures-doc --reload-delay 1000 --no-ui --no-notify", + "watch": "watch-run -d 1000 -p tmpl/**,static/** \"npm run test\"" + }, + "repository": { + "type": "git", + "url": "https://github.com/clenemt/docdash.git" + }, + "devDependencies": { + "jsdoc": "latest", + "browser-sync": "latest", + "watch-run": "latest" + }, + "author": "Clement Moron ", + "license": "Apache-2.0", + "keywords": [ + "jsdoc", + "template" + ], + "files": [ + "publish.js", + "static", + "tmpl" + ] +} diff --git a/docdash-template/publish.js b/docdash-template/publish.js new file mode 100644 index 00000000..049d146f --- /dev/null +++ b/docdash-template/publish.js @@ -0,0 +1,799 @@ +/*global env: true */ +'use strict'; + +var doop = require('jsdoc/util/doop'); +var fs = require('jsdoc/fs'); +var helper = require('jsdoc/util/templateHelper'); +var logger = require('jsdoc/util/logger'); +var path = require('jsdoc/path'); +var taffy = require('taffydb').taffy; +var template = require('jsdoc/template'); +var util = require('util'); + +var htmlsafe = helper.htmlsafe; +var linkto = helper.linkto; +var resolveAuthorLinks = helper.resolveAuthorLinks; +var scopeToPunc = helper.scopeToPunc; +var hasOwnProp = Object.prototype.hasOwnProperty; + +var data; +var view; + +var outdir = path.normalize(env.opts.destination); + +function copyFile(source, target, cb) { + var cbCalled = false; + + var rd = fs.createReadStream(source); + rd.on("error", function(err) { + done(err); + }); + var wr = fs.createWriteStream(target); + wr.on("error", function(err) { + done(err); + }); + wr.on("close", function(ex) { + done(); + }); + rd.pipe(wr); + + function done(err) { + if (!cbCalled) { + cb(err); + cbCalled = true; + } + } +} + +function find(spec) { + return helper.find(data, spec); +} + +function tutoriallink(tutorial) { + return helper.toTutorial(tutorial, null, { tag: 'em', classname: 'disabled', prefix: 'Tutorial: ' }); +} + +function getAncestorLinks(doclet) { + return helper.getAncestorLinks(data, doclet); +} + +function hashToLink(doclet, hash) { + if ( !/^(#.+)/.test(hash) ) { return hash; } + + var url = helper.createLink(doclet); + + url = url.replace(/(#.+|$)/, hash); + return '' + hash + ''; +} + +function needsSignature(doclet) { + var needsSig = false; + + // function and class definitions always get a signature + if (doclet.kind === 'function' || doclet.kind === 'class' && !doclet.hideconstructor) { + needsSig = true; + } + // typedefs that contain functions get a signature, too + else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names && + doclet.type.names.length) { + for (var i = 0, l = doclet.type.names.length; i < l; i++) { + if (doclet.type.names[i].toLowerCase() === 'function') { + needsSig = true; + break; + } + } + } + // and namespaces that are functions get a signature (but finding them is a + // bit messy) + else if (doclet.kind === 'namespace' && doclet.meta && doclet.meta.code && + doclet.meta.code.type && doclet.meta.code.type.match(/[Ff]unction/)) { + needsSig = true; + } + + return needsSig; +} + +function getSignatureAttributes(item) { + var attributes = []; + + if (item.optional) { + attributes.push('opt'); + } + + if (item.nullable === true) { + attributes.push('nullable'); + } + else if (item.nullable === false) { + attributes.push('non-null'); + } + + return attributes; +} + +function updateItemName(item) { + var attributes = getSignatureAttributes(item); + var itemName = item.name || ''; + + if (item.variable) { + itemName = '…' + itemName; + } + + if (attributes && attributes.length) { + itemName = util.format( '%s%s', itemName, + attributes.join(', ') ); + } + + return itemName; +} + +function addParamAttributes(params) { + return params.filter(function(param) { + return param.name && param.name.indexOf('.') === -1; + }).map(updateItemName); +} + +function buildItemTypeStrings(item) { + var types = []; + + if (item && item.type && item.type.names) { + item.type.names.forEach(function(name) { + types.push( linkto(name, htmlsafe(name)) ); + }); + } + + return types; +} + +function buildAttribsString(attribs) { + var attribsString = ''; + + if (attribs && attribs.length) { + attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) ); + } + + return attribsString; +} + +function addNonParamAttributes(items) { + var types = []; + + items.forEach(function(item) { + types = types.concat( buildItemTypeStrings(item) ); + }); + + return types; +} + +function addSignatureParams(f) { + var params = f.params ? addParamAttributes(f.params) : []; + f.signature = util.format( '%s(%s)', (f.signature || ''), params.join(', ') ); +} + +function addSignatureReturns(f) { + var attribs = []; + var attribsString = ''; + var returnTypes = []; + var returnTypesString = ''; + var source = f.yields || f.returns; + + // jam all the return-type attributes into an array. this could create odd results (for example, + // if there are both nullable and non-nullable return types), but let's assume that most people + // who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa. + if (source) { + source.forEach(function(item) { + helper.getAttribs(item).forEach(function(attrib) { + if (attribs.indexOf(attrib) === -1) { + attribs.push(attrib); + } + }); + }); + + attribsString = buildAttribsString(attribs); + } + + if (source) { + returnTypes = addNonParamAttributes(source); + } + if (returnTypes.length) { + returnTypesString = util.format( ' → %s{%s}', attribsString, returnTypes.join('|') ); + } + + f.signature = '' + (f.signature || '') + '' + + '' + returnTypesString + ''; +} + +function addSignatureTypes(f) { + var types = f.type ? buildItemTypeStrings(f) : []; + + f.signature = (f.signature || '') + '' + + (types.length ? ' :' + types.join('|') : '') + ''; +} + +function addAttribs(f) { + var attribs = helper.getAttribs(f); + var attribsString = buildAttribsString(attribs); + + f.attribs = util.format('%s', attribsString); +} + +function shortenPaths(files, commonPrefix) { + Object.keys(files).forEach(function(file) { + files[file].shortened = files[file].resolved.replace(commonPrefix, '') + // always use forward slashes + .replace(/\\/g, '/'); + }); + + return files; +} + +function getPathFromDoclet(doclet) { + if (!doclet.meta) { + return null; + } + + return doclet.meta.path && doclet.meta.path !== 'null' ? + path.join(doclet.meta.path, doclet.meta.filename) : + doclet.meta.filename; +} + +function generate(type, title, docs, filename, resolveLinks) { + resolveLinks = resolveLinks === false ? false : true; + + var docData = { + type: type, + title: title, + docs: docs + }; + + var outpath = path.join(outdir, filename), + html = view.render('container.tmpl', docData); + + if (resolveLinks) { + html = helper.resolveLinks(html); // turn {@link foo} into foo + } + + fs.writeFileSync(outpath, html, 'utf8'); +} + +function generateSourceFiles(sourceFiles, encoding) { + encoding = encoding || 'utf8'; + Object.keys(sourceFiles).forEach(function(file) { + var source; + // links are keyed to the shortened path in each doclet's `meta.shortpath` property + var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened); + helper.registerLink(sourceFiles[file].shortened, sourceOutfile); + + try { + source = { + kind: 'source', + code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) ) + }; + } + catch(e) { + logger.error('Error while generating source file %s: %s', file, e.message); + } + + generate('Source', sourceFiles[file].shortened, [source], sourceOutfile, false); + }); +} + +/** + * Look for classes or functions with the same name as modules (which indicates that the module + * exports only that class or function), then attach the classes or functions to the `module` + * property of the appropriate module doclets. The name of each class or function is also updated + * for display purposes. This function mutates the original arrays. + * + * @private + * @param {Array.} doclets - The array of classes and functions to + * check. + * @param {Array.} modules - The array of module doclets to search. + */ +function attachModuleSymbols(doclets, modules) { + var symbols = {}; + + // build a lookup table + doclets.forEach(function(symbol) { + symbols[symbol.longname] = symbols[symbol.longname] || []; + symbols[symbol.longname].push(symbol); + }); + + return modules.map(function(module) { + if (symbols[module.longname]) { + module.modules = symbols[module.longname] + // Only show symbols that have a description. Make an exception for classes, because + // we want to show the constructor-signature heading no matter what. + .filter(function(symbol) { + return symbol.description || symbol.kind === 'class'; + }) + .map(function(symbol) { + symbol = doop(symbol); + + if (symbol.kind === 'class' || symbol.kind === 'function' && !symbol.hideconstructor) { + symbol.name = symbol.name.replace('module:', '(require("') + '"))'; + } + + return symbol; + }); + } + }); +} + +function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) { + var nav = ''; + + if (items && items.length) { + var itemsNav = ''; + var docdash = env && env.conf && env.conf.docdash || {}; + var level = typeof docdash.navLevel === 'number' && docdash.navLevel >= 0 ? + docdash.navLevel : + Infinity; + + items.forEach(function(item) { + var displayName; + var methods = find({kind:'function', memberof: item.longname}); + var members = find({kind:'member', memberof: item.longname}); + var conf = env && env.conf || {}; + var classes = ''; + + // show private class? + if (docdash.private === false && item.access === 'private') return; + + // depth to show? + if (item.ancestors && item.ancestors.length > level) { + classes += 'level-hide'; + } + + classes = classes ? ' class="'+ classes + '"' : ''; + itemsNav += ''; + if ( !hasOwnProp.call(item, 'longname') ) { + itemsNav += linktoFn('', item.name); + } else if ( !hasOwnProp.call(itemsSeen, item.longname) ) { + if (conf.templates.default.useLongnameInNav) { + displayName = item.longname; + } else { + displayName = item.name; + } + itemsNav += linktoFn(item.longname, displayName.replace(/\b(module|event):/g, '')); + + if (docdash.static && members.find(function (m) { return m.scope === 'static'; } )) { + itemsNav += "