├── .gitignore ├── gulpfile.js ├── src ├── jsontransformer.js ├── htmltransformer.js ├── filemanager.js ├── transcription.js ├── markdowntransformer.js └── javascriptparser.js ├── index.js ├── LICENSE ├── package.json ├── jade ├── index.jade └── class.jade ├── README.md ├── input ├── view.js ├── controller.js └── model.js ├── dist ├── jsontransformer.js ├── htmltransformer.js ├── filemanager.js ├── transcription.js ├── javascriptparser.js └── markdowntransformer.js └── config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | jspm_packages 3 | bower_components 4 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var babel = require('gulp-babel'); 3 | 4 | gulp.task('build', function() { 5 | return gulp.src("./src/*.js") 6 | .pipe(babel()) 7 | .pipe(gulp.dest("dist")); 8 | }); 9 | 10 | gulp.task('watch', function() { 11 | gulp.watch(['./src/*.js'], ['build']); 12 | }); 13 | 14 | gulp.task('default', ['build', 'watch']); 15 | -------------------------------------------------------------------------------- /src/jsontransformer.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export default class JsonTransformer { 4 | static transform(files) { 5 | let jsonFiles = new Map(); 6 | 7 | for(let [path, file] of files){ 8 | for(let classObject of file.classes){ 9 | jsonFiles.set( 10 | classObject.name.toLowerCase() + '.json', 11 | JSON.stringify(file) 12 | ); 13 | } 14 | } 15 | 16 | return jsonFiles; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/htmltransformer.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import jade from 'jade'; 3 | 4 | export default class HtmlTransformer { 5 | static transform(files, template) { 6 | let htmlFiles = new Map(); 7 | 8 | let navTemplate = jade.compileFile(template + '/index.jade'); 9 | let classTemplate = jade.compileFile(template + '/class.jade'); 10 | 11 | let navigation = navTemplate({files: Array.from(files.values())}); 12 | 13 | for(let [path, file] of files){ 14 | for(let classObject of file.classes){ 15 | htmlFiles.set( 16 | classObject.name.toLowerCase() + '.html', 17 | classTemplate({data: classObject, navigation}) 18 | ); 19 | } 20 | } 21 | 22 | return htmlFiles; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var commander = require('commander'); 4 | var transcription = require('./dist/transcription'); 5 | var packageJson = require('./package.json'); 6 | 7 | commander 8 | .version(packageJson.version) 9 | .option('-i, --input [path]', 'input paths') 10 | .option('-o, --output [path]', 'output paths') 11 | .option('-f, --format [format]', 'format of the output', /^(html|md|json)$/i) 12 | .option('-t, --template [path]', 'template path') 13 | .parse(process.argv); 14 | 15 | if(commander.input !== undefined){ 16 | transcription.transformFiles( 17 | commander.input 18 | ,commander.output 19 | ,commander.format 20 | ,commander.template 21 | ); 22 | }else{ 23 | transcription.transformStdin( 24 | commander.output 25 | ,commander.format 26 | ,commander.template 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Affirmix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transcription", 3 | "version": "0.2.1", 4 | "description": "Documentation generator for ES6.", 5 | "keywords": [ 6 | "transcription", 7 | "documentation", 8 | "ecmascript262", 9 | "ecmascript6", 10 | "es6" 11 | ], 12 | "homepage": "https://github.com/affirmix/transcription", 13 | "bugs": { 14 | "url": "https://github.com/affirmix/transcription/issues" 15 | }, 16 | "author": "Andrew Odri (http://affirmix.com)", 17 | "license": "MIT", 18 | "preferGlobal": true, 19 | "bin": { 20 | "transcription": "index.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/affirmix/transcription.git" 25 | }, 26 | "devDependencies": { 27 | "gulp": "^3.8.11", 28 | "gulp-babel": "^4.0.0", 29 | "jspm": "^0.14.0" 30 | }, 31 | "dependencies": { 32 | "acorn": "^0.12.0", 33 | "babel": "^4.6.1", 34 | "commander": "^2.7.1", 35 | "doctrine": "^0.6.4", 36 | "fs": "^0.0.2", 37 | "jade": "^1.9.2", 38 | "markdown": "^0.5.0", 39 | "util": "^0.10.3" 40 | }, 41 | "jspm": { 42 | "dependencies": { 43 | "bootstrap": "github:twbs/bootstrap@^3.3.2" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/filemanager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import JavascriptParser from './javascriptparser'; 4 | 5 | export default class FileManager { 6 | static getFiles(path) { 7 | let files = new Map(); 8 | let pathStat = fs.statSync(path); 9 | 10 | if(pathStat.isFile()){ 11 | files.set( 12 | path, 13 | new JavascriptParser(fs.readFileSync(path, {encoding: 'utf8'})) 14 | ); 15 | }else if(pathStat.isDirectory()){ 16 | for(let file of fs.readdirSync(path)){ 17 | if(fs.statSync(path + '/' + file).isFile() && file.match(/\.js$/) !== null){ 18 | files.set( 19 | path + '/' + file, 20 | new JavascriptParser(fs.readFileSync(path + '/' + file, {encoding: 'utf8'})) 21 | ); 22 | } 23 | } 24 | }else{ 25 | throw new Error("Invalid input path supplied to " + this.constructor.name); 26 | } 27 | 28 | return files; 29 | } 30 | 31 | static writeFiles(files, output) { 32 | if(fs.existsSync(output) && fs.statSync(output).isDirectory()){ 33 | for(let [path, file] of files){ 34 | fs.writeFileSync(output + '/' + path, file, {encoding: 'utf8'}); 35 | } 36 | }else{ 37 | for(let [path, file] of files){ 38 | fs.writeFileSync(output, file, {encoding: 'utf8'}); 39 | break; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jade/index.jade: -------------------------------------------------------------------------------- 1 | mixin parameters(classObject, method, methodType) 2 | - var params = method.comments.tags.filter(function(x){ return x.title == "param" }) 3 | each param, index in params 4 | if index > 0 5 | | ,  6 | a(href=classObject.name.toLowerCase() + ".html#" + methodType + method.name + "-param-" + param.name)= param.name 7 | 8 | .navigation.col-md-4 9 | dl 10 | each file in files 11 | each classObject in file.classes 12 | dt(class=classObject.isPublic ? "public" : "", class=classObject.isDefault ? "default" : "") 13 | a(href=classObject.name.toLowerCase() + ".html")= classObject.name 14 | each method in classObject.methods 15 | if method.isStatic 16 | - var methodType = "static-" 17 | else if method.isGenerator 18 | - var methodType = "generator-" 19 | else 20 | - var methodType = "function-" 21 | if method.isGetter 22 | - methodType += "getter-" 23 | else if method.isSetter 24 | - methodType += "setter-" 25 | 26 | dd 27 | if method.isStatic 28 | | static  29 | if method.isGenerator 30 | | generator  31 | if method.isGetter 32 | | getter  33 | if method.isSetter 34 | | setter  35 | a(href=classObject.name.toLowerCase() + ".html#" + methodType + method.name)= method.name 36 | | ( 37 | +parameters(classObject, method, methodType) 38 | | ) 39 | -------------------------------------------------------------------------------- /jade/class.jade: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | style 4 | include ../jspm_packages/github/twbs/bootstrap@3.3.2/css/bootstrap.min.css 5 | include ../jspm_packages/github/twbs/bootstrap@3.3.2/css/bootstrap-theme.min.css 6 | style. 7 | code { display: inline-block; white-space: pre-wrap; overflow-x: scroll; } 8 | @media (min-width: 992px) { .navigation { position: fixed; } } 9 | title= data.name 10 | body 11 | .container 12 | .row!= navigation 13 | .col-md-8.col-md-offset-4 14 | h1(class=data.isPublic ? "public" : "", class=data.isDefault ? "default" : "")= data.name 15 | p!= data.comments.parsed 16 | h2 Inheritance Hierarchy 17 | p= data.extends ? data.extends : "Object" 18 | h2 Functions 19 | each method in data.methods 20 | if method.isStatic 21 | - var methodType = "static-" 22 | else if method.isGenerator 23 | - var methodType = "generator-" 24 | else 25 | - var methodType = "function-" 26 | if method.isGetter 27 | - methodType += "getter-" 28 | else if method.isSetter 29 | - methodType += "setter-" 30 | 31 | h3(id=methodType + method.name, class=method.isConstructor ? "constructor" : "", class=method.isStatic ? "static" : "", class=method.isGetter ? "getter" : "", class=method.isSetter ? "setter" : "", class=method.isGenerator ? "generator" : "") 32 | if method.isStatic 33 | small static  34 | if method.isGetter 35 | small getter  36 | if method.isSetter 37 | small setter  38 | if method.isGenerator 39 | small generator  40 | | #{method.name} 41 | p!= method.comments.parsed 42 | - var params = method.comments.tags.filter(function(x){ return x.title == "param" }) 43 | if params.length > 0 44 | h4 Parameters 45 | dl 46 | each param in params 47 | dt(id=methodType + method.name + "-param-" + param.name)= param.name 48 | dd= param.description 49 | -------------------------------------------------------------------------------- /src/transcription.js: -------------------------------------------------------------------------------- 1 | import polyfill from 'babel/polyfill'; 2 | 3 | import FileManager from './filemanager'; 4 | import JavascriptParser from './javascriptparser'; 5 | import JsonTransformer from './jsontransformer'; 6 | import MarkdownTransformer from './markdowntransformer'; 7 | import HtmlTransformer from './htmltransformer'; 8 | 9 | export default class Transcription { 10 | static transformFiles(input, output, format = 'html', template = './jade') { 11 | let files = FileManager.getFiles(input); 12 | let transformedFiles = new Map(); 13 | 14 | switch(format){ 15 | case "json": 16 | transformedFiles = JsonTransformer.transform(files); 17 | break; 18 | case "md": 19 | transformedFiles = MarkdownTransformer.transform(files); 20 | break; 21 | case "html": 22 | transformedFiles = HtmlTransformer.transform(files, template); 23 | break; 24 | } 25 | 26 | if(output !== undefined){ 27 | Transcription.writeFiles(transformedFiles, output); 28 | }else{ 29 | Transcription.writeStdout(transformedFiles); 30 | } 31 | } 32 | 33 | static transformStdin(output, format = 'html', template = './jade') { 34 | let data = ""; 35 | let stdin = new Map(); 36 | 37 | process.stdin.on('readable', () => { data += process.stdin.read() }); 38 | 39 | process.stdin.on('end', function() { 40 | let transformedFiles = new Map(); 41 | stdin.set('stdin', new JavascriptParser(data)); 42 | 43 | switch(format){ 44 | case "json": 45 | transformedFiles = JsonTransformer.transform(stdin); 46 | break; 47 | case "md": 48 | transformedFiles = MarkdownTransformer.transform(stdin); 49 | break; 50 | case "html": 51 | transformedFiles = HtmlTransformer.transform(stdin, template); 52 | break; 53 | } 54 | 55 | if(output !== undefined){ 56 | Transcription.writeFiles(transformedFiles, output); 57 | }else{ 58 | Transcription.writeStdout(transformedFiles); 59 | } 60 | }); 61 | } 62 | 63 | static writeFiles(transformedFiles, output){ 64 | FileManager.writeFiles(transformedFiles, output); 65 | } 66 | 67 | static writeStdout(transformedFiles){ 68 | for(let [id, data] of transformedFiles){ 69 | process.stdout.write(data); 70 | break; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/markdowntransformer.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import util from 'util'; 3 | 4 | export default class MarkdownTransformer { 5 | static transform(files) { 6 | let markdownFiles = new Map(); 7 | 8 | let navigation = MarkdownTransformer.buildNavigation(files); 9 | 10 | for(let [path, file] of files){ 11 | for(let classObject of file.classes){ 12 | markdownFiles.set( 13 | classObject.name.toLowerCase() + '.md', 14 | navigation + MarkdownTransformer.buildClass(classObject) 15 | ); 16 | } 17 | } 18 | 19 | return markdownFiles; 20 | } 21 | 22 | static buildNavigation(files) { 23 | let result = ""; 24 | 25 | let classHeading = "* %s\n"; 26 | let methodWrapper = " * %s(%s)\n"; 27 | let parameter = "[%s](%s)"; 28 | 29 | for(let [path, file] of files){ 30 | for(let classObject of file.classes){ 31 | result += util.format(classHeading, classObject.name); 32 | 33 | for(let method of classObject.methods){ 34 | let params = ""; 35 | for(let tag of method.comments.tags){ 36 | if(tag.title == "param"){ 37 | params += util.format(parameter, tag.name, ''); 38 | } 39 | } 40 | 41 | result += util.format(methodWrapper, method.name, params); 42 | } 43 | 44 | result += '\n'; 45 | } 46 | } 47 | 48 | return result; 49 | } 50 | 51 | static buildClass(classObject) { 52 | let result = ""; 53 | 54 | let classHeading = "# %s\n\n"; 55 | let methodHeading = "## %s\n\n"; 56 | let paragraph = "%s\n\n"; 57 | let tableWrapper = "%s
\n\n"; 58 | let parameter = "%s%s%s%s"; 59 | 60 | result += util.format(classHeading, classObject.name); 61 | result += util.format(paragraph, classObject.comments.description); 62 | 63 | for(let method of classObject.methods){ 64 | result += util.format(methodHeading, method.name); 65 | result += util.format(paragraph, method.comments.description); 66 | 67 | let table = ""; 68 | for(let tag of method.comments.tags){ 69 | table += util.format(parameter, tag.title, tag.name, tag.type ? tag.type.name : "", tag.description); 70 | } 71 | 72 | result += util.format(tableWrapper, table); 73 | } 74 | 75 | return result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/affirmix/transcription.svg)](https://travis-ci.org/affirmix/transcription) [![Coverage Status](https://coveralls.io/repos/affirmix/transcription/badge.svg)](https://coveralls.io/r/affirmix/transcription) [![Dependency Status](https://david-dm.org/affirmix/transcription.svg)](https://david-dm.org/affirmix/transcription) [![devDependency Status](https://david-dm.org/affirmix/transcription/dev-status.svg)](https://david-dm.org/affirmix/transcription#info=devDependencies) [![Gitter](https://img.shields.io/badge/gitter-join%20chat%E2%86%92-brightgreen.svg)](https://gitter.im/affirmix/transcription?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) 2 | 3 | ## What is Transcription? 4 | 5 | Transcription is an ECMAScript 6 (ES6) documentation generator designed to provide JSON, Markdown, and template driven HTML output. 6 | 7 | ## What can I expect from Transcription as development continues? 8 | 9 | 1. *Mutliple output formats*. Flexible options should provided as each project and enviorment is unique. Transcription will out JSON structures (See Dox), Markdown (See Markdox), and template driven HTML output (See JSDoc). 10 | 11 | 2. *Dynamically linked projects and classes*. One feature missing from many JavaScript documentation generators in linking to projects and classes outside of the scope of one single file. This is a feature that Transcription will aim to provide. 12 | 13 | 3. *Verify AST structures against JSDoc params*. Becuase we have a proper AST object to compare with JSDoc doclets, an analysis will be peformed to verfiy that your comments matches the elements they are describing. This will developers track down any comments that become out of date, and inconsistencies that develop. 14 | 15 | 4. *Gulp npm module*. After basic functionality has been implemented and tested, Transcription should be easy to integrate into your build system. 16 | 17 | ## How can I use Transcription? 18 | 19 | You can see Transcription in action on your own system by following these steps: (Dependant on Git and Node.js) 20 | 21 | 1. `npm install transcription` 22 | 2. One of the following commands: (For JSON, Markdown, and HTML respectively) 23 | * `transcription --help` 24 | * `transcription -f html -i ./input -o ./output` 25 | * `transcription -f html -o ./output.html < ./input/controller.js` 26 | * `transcription -f html -o < ./input/controller.js > ./output/controller.html` 27 | * `transcription -f html -i ./input.html > ./output/controller.js` 28 | -------------------------------------------------------------------------------- /input/view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the base view in TungstenJS. It provides a simple template property that can be overridden, and a render function that will chain any deferred responses. 3 | * 4 | * This view utilizes very basic template literals that are native to ECMAScript 6. This view is intended to be extended for use with other templating systems, for example `EJSView`, `MustacheView`, and `UnderscoreView` that are under development. 5 | * 6 | * @class View 7 | * @author Andrew Odri andrew@affirmix.com 8 | */ 9 | export class View { 10 | /** 11 | * This returns a reference that whatever the top-most sub-class is, which comes in handy when managing instances in static functions on classes that are designed to be extended. 12 | * 13 | * @static 14 | * @property {Class} classReference Reference to the current class 15 | */ 16 | static get classReference() { return eval(this.name); } 17 | 18 | /** 19 | * This returns a reference that whatever the top-most sub-class is, which comes in handy when managing instances in static functions on classes that are designed to be extended. 20 | * 21 | * @property {Class} classReference Reference to the current class 22 | */ 23 | get classReference() { return eval(this.constructor.name); } 24 | 25 | /** 26 | * This function is called by the render function, providing with the data that is returned from the resolved promise object. While the template is currently implemented as an ECMAScript 6 template literal, it could also just return a path if the render function has been implemented with a 3rd party renderer. 27 | * 28 | * @static 29 | * @param {Object} data This is data returned from the resolved promise in the render function 30 | * @returns {String} String containing the the HTML render buy the temaplte based on the data provided in the data parameter 31 | */ 32 | static template(data) { 33 | console.log('View.template()'); 34 | 35 | return ``; 36 | } 37 | 38 | /** 39 | * This function take care of managing the rendering of the view. The bulk of the logic should be stored in the function if it is to be overriden for a 3rd party renderer. This allows the template object to be as simple as possible, so that it be overridden with just a simple template or path for real world view implementations. 40 | * 41 | * @static 42 | * @param {Object} request The request is a deferred object containing the data to be rendered by the view. Usually this is a deferred AJAX object returned by the model, but could be any appropriate object. 43 | * @return {Object} Returns a deferred object containing the rendered view HTML after it has been applied to the template in the in the template function 44 | */ 45 | static render(request) { 46 | console.log('View.render()'); 47 | 48 | let deferred = $.Deferred(); 49 | 50 | $.when(request).done( 51 | (data, textStatus, jqXHR) => deferred.resolve(this.template(data)) 52 | ); 53 | 54 | return deferred.promise(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dist/jsontransformer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; 4 | 5 | var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; 6 | 7 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 8 | 9 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 10 | 11 | var fs = _interopRequire(require("fs")); 12 | 13 | var JsonTransformer = (function () { 14 | function JsonTransformer() { 15 | _classCallCheck(this, JsonTransformer); 16 | } 17 | 18 | _prototypeProperties(JsonTransformer, { 19 | transform: { 20 | value: function transform(files) { 21 | var jsonFiles = new Map(); 22 | 23 | var _iteratorNormalCompletion = true; 24 | var _didIteratorError = false; 25 | var _iteratorError = undefined; 26 | 27 | try { 28 | for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 29 | var _step$value = _slicedToArray(_step.value, 2); 30 | 31 | var path = _step$value[0]; 32 | var file = _step$value[1]; 33 | var _iteratorNormalCompletion2 = true; 34 | var _didIteratorError2 = false; 35 | var _iteratorError2 = undefined; 36 | 37 | try { 38 | for (var _iterator2 = file.classes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 39 | var classObject = _step2.value; 40 | 41 | jsonFiles.set(classObject.name.toLowerCase() + ".json", JSON.stringify(file)); 42 | } 43 | } catch (err) { 44 | _didIteratorError2 = true; 45 | _iteratorError2 = err; 46 | } finally { 47 | try { 48 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 49 | _iterator2["return"](); 50 | } 51 | } finally { 52 | if (_didIteratorError2) { 53 | throw _iteratorError2; 54 | } 55 | } 56 | } 57 | } 58 | } catch (err) { 59 | _didIteratorError = true; 60 | _iteratorError = err; 61 | } finally { 62 | try { 63 | if (!_iteratorNormalCompletion && _iterator["return"]) { 64 | _iterator["return"](); 65 | } 66 | } finally { 67 | if (_didIteratorError) { 68 | throw _iteratorError; 69 | } 70 | } 71 | } 72 | 73 | return jsonFiles; 74 | }, 75 | writable: true, 76 | configurable: true 77 | } 78 | }); 79 | 80 | return JsonTransformer; 81 | })(); 82 | 83 | module.exports = JsonTransformer; -------------------------------------------------------------------------------- /src/javascriptparser.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import util from 'util'; 3 | import acorn from 'acorn'; 4 | import doctrine from 'doctrine'; 5 | import markdown from 'markdown'; 6 | 7 | export default class JavascriptParser { 8 | constructor(data) { 9 | let comments = []; 10 | 11 | this.ast = acorn.parse(data, { 12 | ecmaVersion: 6, 13 | onComment: (block, text, start, end) => { if(block) comments.push({block, text, start, end}) } 14 | }); 15 | 16 | this.comments = comments[Symbol.iterator](); 17 | this.currentComment = this.comments.next(); 18 | 19 | //console.log(util.inspect(this.ast.body, false, 5)); 20 | 21 | //console.log(util.inspect(this.transform(this.ast.body), false, 10)); 22 | 23 | //console.log(comments); 24 | 25 | let transformed = this.getJavascript(this.ast.body); 26 | 27 | this.imports = transformed.filter((element) => element.type == 'import'); 28 | this.classes = transformed.filter((element) => element.type == 'export').map((element) => element.variables[0]); 29 | } 30 | 31 | getJavascript(parent){ 32 | let result = []; 33 | let position = 0; 34 | 35 | for(let node of parent){ 36 | switch(node.type){ 37 | case 'ImportDeclaration': 38 | result.push({ 39 | type: 'import', 40 | name: node.specifiers[0].id.name, 41 | path: node.source.value 42 | }); 43 | break; 44 | case 'ExportDeclaration': 45 | Object.defineProperty(node.declaration, "export", { value: true }); 46 | Object.defineProperty(node.declaration, "default", { value: node.default }); 47 | 48 | result.push({ 49 | type: 'export', 50 | variables: this.getJavascript([ 51 | node.declaration 52 | ]) 53 | }); 54 | break; 55 | case 'ClassDeclaration': 56 | result.push({ 57 | type: 'class', 58 | name: node.id.name, 59 | extends: node.superClass !== null ? node.superClass.name : null, 60 | comments: this.getComments(position, node.start), 61 | methods: this.getJavascript(node.body.body), 62 | isPublic: node.hasOwnProperty('export') && node.export, 63 | isDefault: node.hasOwnProperty('default') && node.default 64 | }); 65 | break; 66 | case 'MethodDefinition': 67 | result.push({ 68 | type: 'function', 69 | name: node.key.name, 70 | params: Array.from(node.value.params, (x) => x.name), 71 | comments: this.getComments(position, node.start), 72 | isConstructor: !node.static && node.key.name == 'constructor', 73 | isStatic: node.static, 74 | isGetter: node.hasOwnProperty('kind') && node.kind == 'get', 75 | isSetter: node.hasOwnProperty('kind') && node.kind == 'set', 76 | isGenerator: node.value.generator 77 | }); 78 | break; 79 | } 80 | 81 | position = node.end; 82 | } 83 | 84 | return result; 85 | } 86 | 87 | getComments(prevEnd, nextStart) { 88 | if(!this.currentComment.done && prevEnd <= this.currentComment.value.start && nextStart > this.currentComment.value.end){ 89 | let comments = doctrine.parse(this.currentComment.value.text, { 90 | unwrap: true, 91 | recoverable: true 92 | }); 93 | 94 | comments.parsed = markdown.markdown.toHTML(comments.description); 95 | 96 | this.currentComment = this.comments.next(); 97 | 98 | return comments; 99 | }else{ 100 | return null; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /dist/htmltransformer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; 4 | 5 | var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; 6 | 7 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 8 | 9 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 10 | 11 | var fs = _interopRequire(require("fs")); 12 | 13 | var jade = _interopRequire(require("jade")); 14 | 15 | var HtmlTransformer = (function () { 16 | function HtmlTransformer() { 17 | _classCallCheck(this, HtmlTransformer); 18 | } 19 | 20 | _prototypeProperties(HtmlTransformer, { 21 | transform: { 22 | value: function transform(files, template) { 23 | var htmlFiles = new Map(); 24 | 25 | var navTemplate = jade.compileFile(template + "/index.jade"); 26 | var classTemplate = jade.compileFile(template + "/class.jade"); 27 | 28 | var navigation = navTemplate({ files: Array.from(files.values()) }); 29 | 30 | var _iteratorNormalCompletion = true; 31 | var _didIteratorError = false; 32 | var _iteratorError = undefined; 33 | 34 | try { 35 | for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 36 | var _step$value = _slicedToArray(_step.value, 2); 37 | 38 | var path = _step$value[0]; 39 | var file = _step$value[1]; 40 | var _iteratorNormalCompletion2 = true; 41 | var _didIteratorError2 = false; 42 | var _iteratorError2 = undefined; 43 | 44 | try { 45 | for (var _iterator2 = file.classes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 46 | var classObject = _step2.value; 47 | 48 | htmlFiles.set(classObject.name.toLowerCase() + ".html", classTemplate({ data: classObject, navigation: navigation })); 49 | } 50 | } catch (err) { 51 | _didIteratorError2 = true; 52 | _iteratorError2 = err; 53 | } finally { 54 | try { 55 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 56 | _iterator2["return"](); 57 | } 58 | } finally { 59 | if (_didIteratorError2) { 60 | throw _iteratorError2; 61 | } 62 | } 63 | } 64 | } 65 | } catch (err) { 66 | _didIteratorError = true; 67 | _iteratorError = err; 68 | } finally { 69 | try { 70 | if (!_iteratorNormalCompletion && _iterator["return"]) { 71 | _iterator["return"](); 72 | } 73 | } finally { 74 | if (_didIteratorError) { 75 | throw _iteratorError; 76 | } 77 | } 78 | } 79 | 80 | return htmlFiles; 81 | }, 82 | writable: true, 83 | configurable: true 84 | } 85 | }); 86 | 87 | return HtmlTransformer; 88 | })(); 89 | 90 | module.exports = HtmlTransformer; -------------------------------------------------------------------------------- /dist/filemanager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; 4 | 5 | var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; 6 | 7 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 8 | 9 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 10 | 11 | var fs = _interopRequire(require("fs")); 12 | 13 | var JavascriptParser = _interopRequire(require("./javascriptparser")); 14 | 15 | var FileManager = (function () { 16 | function FileManager() { 17 | _classCallCheck(this, FileManager); 18 | } 19 | 20 | _prototypeProperties(FileManager, { 21 | getFiles: { 22 | value: function getFiles(path) { 23 | var files = new Map(); 24 | var pathStat = fs.statSync(path); 25 | 26 | if (pathStat.isFile()) { 27 | files.set(path, new JavascriptParser(fs.readFileSync(path, { encoding: "utf8" }))); 28 | } else if (pathStat.isDirectory()) { 29 | var _iteratorNormalCompletion = true; 30 | var _didIteratorError = false; 31 | var _iteratorError = undefined; 32 | 33 | try { 34 | for (var _iterator = fs.readdirSync(path)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 35 | var file = _step.value; 36 | 37 | if (fs.statSync(path + "/" + file).isFile() && file.match(/\.js$/) !== null) { 38 | files.set(path + "/" + file, new JavascriptParser(fs.readFileSync(path + "/" + file, { encoding: "utf8" }))); 39 | } 40 | } 41 | } catch (err) { 42 | _didIteratorError = true; 43 | _iteratorError = err; 44 | } finally { 45 | try { 46 | if (!_iteratorNormalCompletion && _iterator["return"]) { 47 | _iterator["return"](); 48 | } 49 | } finally { 50 | if (_didIteratorError) { 51 | throw _iteratorError; 52 | } 53 | } 54 | } 55 | } else { 56 | throw new Error("Invalid input path supplied to " + this.constructor.name); 57 | } 58 | 59 | return files; 60 | }, 61 | writable: true, 62 | configurable: true 63 | }, 64 | writeFiles: { 65 | value: function writeFiles(files, output) { 66 | if (fs.existsSync(output) && fs.statSync(output).isDirectory()) { 67 | var _iteratorNormalCompletion = true; 68 | var _didIteratorError = false; 69 | var _iteratorError = undefined; 70 | 71 | try { 72 | for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 73 | var _step$value = _slicedToArray(_step.value, 2); 74 | 75 | var path = _step$value[0]; 76 | var file = _step$value[1]; 77 | 78 | fs.writeFileSync(output + "/" + path, file, { encoding: "utf8" }); 79 | } 80 | } catch (err) { 81 | _didIteratorError = true; 82 | _iteratorError = err; 83 | } finally { 84 | try { 85 | if (!_iteratorNormalCompletion && _iterator["return"]) { 86 | _iterator["return"](); 87 | } 88 | } finally { 89 | if (_didIteratorError) { 90 | throw _iteratorError; 91 | } 92 | } 93 | } 94 | } else { 95 | var _iteratorNormalCompletion2 = true; 96 | var _didIteratorError2 = false; 97 | var _iteratorError2 = undefined; 98 | 99 | try { 100 | for (var _iterator2 = files[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 101 | var _step2$value = _slicedToArray(_step2.value, 2); 102 | 103 | var path = _step2$value[0]; 104 | var file = _step2$value[1]; 105 | 106 | fs.writeFileSync(output, file, { encoding: "utf8" }); 107 | if (_iterator2["return"]) _iterator2["return"](); 108 | break; 109 | } 110 | } catch (err) { 111 | _didIteratorError2 = true; 112 | _iteratorError2 = err; 113 | } finally { 114 | try { 115 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 116 | _iterator2["return"](); 117 | } 118 | } finally { 119 | if (_didIteratorError2) { 120 | throw _iteratorError2; 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | writable: true, 127 | configurable: true 128 | } 129 | }); 130 | 131 | return FileManager; 132 | })(); 133 | 134 | module.exports = FileManager; -------------------------------------------------------------------------------- /dist/transcription.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; 4 | 5 | var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; 6 | 7 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 8 | 9 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 10 | 11 | var polyfill = _interopRequire(require("babel/polyfill")); 12 | 13 | var FileManager = _interopRequire(require("./filemanager")); 14 | 15 | var JavascriptParser = _interopRequire(require("./javascriptparser")); 16 | 17 | var JsonTransformer = _interopRequire(require("./jsontransformer")); 18 | 19 | var MarkdownTransformer = _interopRequire(require("./markdowntransformer")); 20 | 21 | var HtmlTransformer = _interopRequire(require("./htmltransformer")); 22 | 23 | var Transcription = (function () { 24 | function Transcription() { 25 | _classCallCheck(this, Transcription); 26 | } 27 | 28 | _prototypeProperties(Transcription, { 29 | transformFiles: { 30 | value: function transformFiles(input, output) { 31 | var format = arguments[2] === undefined ? "html" : arguments[2]; 32 | var template = arguments[3] === undefined ? "./jade" : arguments[3]; 33 | 34 | var files = FileManager.getFiles(input); 35 | var transformedFiles = new Map(); 36 | 37 | switch (format) { 38 | case "json": 39 | transformedFiles = JsonTransformer.transform(files); 40 | break; 41 | case "md": 42 | transformedFiles = MarkdownTransformer.transform(files); 43 | break; 44 | case "html": 45 | transformedFiles = HtmlTransformer.transform(files, template); 46 | break; 47 | } 48 | 49 | if (output !== undefined) { 50 | Transcription.writeFiles(transformedFiles, output); 51 | } else { 52 | Transcription.writeStdout(transformedFiles); 53 | } 54 | }, 55 | writable: true, 56 | configurable: true 57 | }, 58 | transformStdin: { 59 | value: function transformStdin(output) { 60 | var format = arguments[1] === undefined ? "html" : arguments[1]; 61 | var template = arguments[2] === undefined ? "./jade" : arguments[2]; 62 | 63 | var data = ""; 64 | var stdin = new Map(); 65 | 66 | process.stdin.on("readable", function () { 67 | data += process.stdin.read(); 68 | }); 69 | 70 | process.stdin.on("end", function () { 71 | var transformedFiles = new Map(); 72 | stdin.set("stdin", new JavascriptParser(data)); 73 | 74 | switch (format) { 75 | case "json": 76 | transformedFiles = JsonTransformer.transform(stdin); 77 | break; 78 | case "md": 79 | transformedFiles = MarkdownTransformer.transform(stdin); 80 | break; 81 | case "html": 82 | transformedFiles = HtmlTransformer.transform(stdin, template); 83 | break; 84 | } 85 | 86 | if (output !== undefined) { 87 | Transcription.writeFiles(transformedFiles, output); 88 | } else { 89 | Transcription.writeStdout(transformedFiles); 90 | } 91 | }); 92 | }, 93 | writable: true, 94 | configurable: true 95 | }, 96 | writeFiles: { 97 | value: function writeFiles(transformedFiles, output) { 98 | FileManager.writeFiles(transformedFiles, output); 99 | }, 100 | writable: true, 101 | configurable: true 102 | }, 103 | writeStdout: { 104 | value: function writeStdout(transformedFiles) { 105 | var _iteratorNormalCompletion = true; 106 | var _didIteratorError = false; 107 | var _iteratorError = undefined; 108 | 109 | try { 110 | for (var _iterator = transformedFiles[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 111 | var _step$value = _slicedToArray(_step.value, 2); 112 | 113 | var id = _step$value[0]; 114 | var data = _step$value[1]; 115 | 116 | process.stdout.write(data); 117 | if (_iterator["return"]) _iterator["return"](); 118 | break; 119 | } 120 | } catch (err) { 121 | _didIteratorError = true; 122 | _iteratorError = err; 123 | } finally { 124 | try { 125 | if (!_iteratorNormalCompletion && _iterator["return"]) { 126 | _iterator["return"](); 127 | } 128 | } finally { 129 | if (_didIteratorError) { 130 | throw _iteratorError; 131 | } 132 | } 133 | } 134 | }, 135 | writable: true, 136 | configurable: true 137 | } 138 | }); 139 | 140 | return Transcription; 141 | })(); 142 | 143 | module.exports = Transcription; -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | "paths": { 3 | "*": "*.js", 4 | "github:*": "jspm_packages/github/*.js", 5 | "npm:*": "jspm_packages/npm/*.js" 6 | } 7 | }); 8 | 9 | System.config({ 10 | "map": { 11 | "bootstrap": "github:twbs/bootstrap@3.3.2", 12 | "github:jspm/nodelibs-assert@0.1.0": { 13 | "assert": "npm:assert@1.3.0" 14 | }, 15 | "github:jspm/nodelibs-buffer@0.1.0": { 16 | "buffer": "npm:buffer@3.1.0" 17 | }, 18 | "github:jspm/nodelibs-events@0.1.0": { 19 | "events-browserify": "npm:events-browserify@0.0.1" 20 | }, 21 | "github:jspm/nodelibs-http@1.7.1": { 22 | "Base64": "npm:Base64@0.2.1", 23 | "events": "github:jspm/nodelibs-events@0.1.0", 24 | "inherits": "npm:inherits@2.0.1", 25 | "stream": "github:jspm/nodelibs-stream@0.1.0", 26 | "url": "github:jspm/nodelibs-url@0.1.0", 27 | "util": "github:jspm/nodelibs-util@0.1.0" 28 | }, 29 | "github:jspm/nodelibs-https@0.1.0": { 30 | "https-browserify": "npm:https-browserify@0.0.0" 31 | }, 32 | "github:jspm/nodelibs-os@0.1.0": { 33 | "os-browserify": "npm:os-browserify@0.1.2" 34 | }, 35 | "github:jspm/nodelibs-path@0.1.0": { 36 | "path-browserify": "npm:path-browserify@0.0.0" 37 | }, 38 | "github:jspm/nodelibs-process@0.1.1": { 39 | "process": "npm:process@0.10.1" 40 | }, 41 | "github:jspm/nodelibs-stream@0.1.0": { 42 | "stream-browserify": "npm:stream-browserify@1.0.0" 43 | }, 44 | "github:jspm/nodelibs-url@0.1.0": { 45 | "url": "npm:url@0.10.3" 46 | }, 47 | "github:jspm/nodelibs-util@0.1.0": { 48 | "util": "npm:util@0.10.3" 49 | }, 50 | "github:systemjs/plugin-css@0.1.6": { 51 | "clean-css": "npm:clean-css@3.0.10", 52 | "fs": "github:jspm/nodelibs-fs@0.1.1", 53 | "path": "github:jspm/nodelibs-path@0.1.0" 54 | }, 55 | "github:twbs/bootstrap@3.3.2": { 56 | "css": "github:systemjs/plugin-css@0.1.6", 57 | "jquery": "github:components/jquery@2.1.3" 58 | }, 59 | "npm:amdefine@0.1.0": { 60 | "fs": "github:jspm/nodelibs-fs@0.1.1", 61 | "module": "github:jspm/nodelibs-module@0.1.0", 62 | "path": "github:jspm/nodelibs-path@0.1.0", 63 | "process": "github:jspm/nodelibs-process@0.1.1" 64 | }, 65 | "npm:assert@1.3.0": { 66 | "util": "npm:util@0.10.3" 67 | }, 68 | "npm:buffer@3.1.0": { 69 | "base64-js": "npm:base64-js@0.0.8", 70 | "ieee754": "npm:ieee754@1.1.4", 71 | "is-array": "npm:is-array@1.0.1" 72 | }, 73 | "npm:clean-css@3.0.10": { 74 | "buffer": "github:jspm/nodelibs-buffer@0.1.0", 75 | "commander": "npm:commander@2.5.1", 76 | "fs": "github:jspm/nodelibs-fs@0.1.1", 77 | "http": "github:jspm/nodelibs-http@1.7.1", 78 | "https": "github:jspm/nodelibs-https@0.1.0", 79 | "os": "github:jspm/nodelibs-os@0.1.0", 80 | "path": "github:jspm/nodelibs-path@0.1.0", 81 | "process": "github:jspm/nodelibs-process@0.1.1", 82 | "source-map": "npm:source-map@0.1.43", 83 | "url": "github:jspm/nodelibs-url@0.1.0", 84 | "util": "github:jspm/nodelibs-util@0.1.0" 85 | }, 86 | "npm:commander@2.5.1": { 87 | "child_process": "github:jspm/nodelibs-child_process@0.1.0", 88 | "events": "github:jspm/nodelibs-events@0.1.0", 89 | "path": "github:jspm/nodelibs-path@0.1.0", 90 | "process": "github:jspm/nodelibs-process@0.1.1" 91 | }, 92 | "npm:core-util-is@1.0.1": { 93 | "buffer": "github:jspm/nodelibs-buffer@0.1.0" 94 | }, 95 | "npm:events-browserify@0.0.1": { 96 | "process": "github:jspm/nodelibs-process@0.1.1" 97 | }, 98 | "npm:https-browserify@0.0.0": { 99 | "http": "github:jspm/nodelibs-http@1.7.1" 100 | }, 101 | "npm:inherits@2.0.1": { 102 | "util": "github:jspm/nodelibs-util@0.1.0" 103 | }, 104 | "npm:os-browserify@0.1.2": { 105 | "os": "github:jspm/nodelibs-os@0.1.0" 106 | }, 107 | "npm:path-browserify@0.0.0": { 108 | "process": "github:jspm/nodelibs-process@0.1.1" 109 | }, 110 | "npm:punycode@1.3.2": { 111 | "process": "github:jspm/nodelibs-process@0.1.1" 112 | }, 113 | "npm:readable-stream@1.1.13": { 114 | "buffer": "github:jspm/nodelibs-buffer@0.1.0", 115 | "core-util-is": "npm:core-util-is@1.0.1", 116 | "events": "github:jspm/nodelibs-events@0.1.0", 117 | "inherits": "npm:inherits@2.0.1", 118 | "isarray": "npm:isarray@0.0.1", 119 | "process": "github:jspm/nodelibs-process@0.1.1", 120 | "stream": "npm:stream-browserify@1.0.0", 121 | "string_decoder": "npm:string_decoder@0.10.31", 122 | "util": "github:jspm/nodelibs-util@0.1.0" 123 | }, 124 | "npm:source-map@0.1.43": { 125 | "amdefine": "npm:amdefine@0.1.0", 126 | "fs": "github:jspm/nodelibs-fs@0.1.1", 127 | "path": "github:jspm/nodelibs-path@0.1.0", 128 | "process": "github:jspm/nodelibs-process@0.1.1" 129 | }, 130 | "npm:stream-browserify@1.0.0": { 131 | "events": "github:jspm/nodelibs-events@0.1.0", 132 | "inherits": "npm:inherits@2.0.1", 133 | "readable-stream": "npm:readable-stream@1.1.13" 134 | }, 135 | "npm:string_decoder@0.10.31": { 136 | "buffer": "github:jspm/nodelibs-buffer@0.1.0" 137 | }, 138 | "npm:url@0.10.3": { 139 | "assert": "github:jspm/nodelibs-assert@0.1.0", 140 | "punycode": "npm:punycode@1.3.2", 141 | "querystring": "npm:querystring@0.2.0", 142 | "util": "github:jspm/nodelibs-util@0.1.0" 143 | }, 144 | "npm:util@0.10.3": { 145 | "inherits": "npm:inherits@2.0.1", 146 | "process": "github:jspm/nodelibs-process@0.1.1" 147 | } 148 | } 149 | }); 150 | 151 | -------------------------------------------------------------------------------- /dist/javascriptparser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; 4 | 5 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 6 | 7 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 8 | 9 | var fs = _interopRequire(require("fs")); 10 | 11 | var util = _interopRequire(require("util")); 12 | 13 | var acorn = _interopRequire(require("acorn")); 14 | 15 | var doctrine = _interopRequire(require("doctrine")); 16 | 17 | var markdown = _interopRequire(require("markdown")); 18 | 19 | var JavascriptParser = (function () { 20 | function JavascriptParser(data) { 21 | _classCallCheck(this, JavascriptParser); 22 | 23 | var comments = []; 24 | 25 | this.ast = acorn.parse(data, { 26 | ecmaVersion: 6, 27 | onComment: function (block, text, start, end) { 28 | if (block) comments.push({ block: block, text: text, start: start, end: end }); 29 | } 30 | }); 31 | 32 | this.comments = comments[Symbol.iterator](); 33 | this.currentComment = this.comments.next(); 34 | 35 | //console.log(util.inspect(this.ast.body, false, 5)); 36 | 37 | //console.log(util.inspect(this.transform(this.ast.body), false, 10)); 38 | 39 | //console.log(comments); 40 | 41 | var transformed = this.getJavascript(this.ast.body); 42 | 43 | this.imports = transformed.filter(function (element) { 44 | return element.type == "import"; 45 | }); 46 | this.classes = transformed.filter(function (element) { 47 | return element.type == "export"; 48 | }).map(function (element) { 49 | return element.variables[0]; 50 | }); 51 | } 52 | 53 | _prototypeProperties(JavascriptParser, null, { 54 | getJavascript: { 55 | value: function getJavascript(parent) { 56 | var result = []; 57 | var position = 0; 58 | 59 | var _iteratorNormalCompletion = true; 60 | var _didIteratorError = false; 61 | var _iteratorError = undefined; 62 | 63 | try { 64 | for (var _iterator = parent[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 65 | var node = _step.value; 66 | 67 | switch (node.type) { 68 | case "ImportDeclaration": 69 | result.push({ 70 | type: "import", 71 | name: node.specifiers[0].id.name, 72 | path: node.source.value 73 | }); 74 | if (_iterator["return"]) _iterator["return"](); 75 | break; 76 | case "ExportDeclaration": 77 | Object.defineProperty(node.declaration, "export", { value: true }); 78 | Object.defineProperty(node.declaration, "default", { value: node["default"] }); 79 | 80 | result.push({ 81 | type: "export", 82 | variables: this.getJavascript([node.declaration]) 83 | }); 84 | if (_iterator["return"]) _iterator["return"](); 85 | break; 86 | case "ClassDeclaration": 87 | result.push({ 88 | type: "class", 89 | name: node.id.name, 90 | "extends": node.superClass !== null ? node.superClass.name : null, 91 | comments: this.getComments(position, node.start), 92 | methods: this.getJavascript(node.body.body), 93 | isPublic: node.hasOwnProperty("export") && node["export"], 94 | isDefault: node.hasOwnProperty("default") && node["default"] 95 | }); 96 | if (_iterator["return"]) _iterator["return"](); 97 | break; 98 | case "MethodDefinition": 99 | result.push({ 100 | type: "function", 101 | name: node.key.name, 102 | params: Array.from(node.value.params, function (x) { 103 | return x.name; 104 | }), 105 | comments: this.getComments(position, node.start), 106 | isConstructor: !node["static"] && node.key.name == "constructor", 107 | isStatic: node["static"], 108 | isGetter: node.hasOwnProperty("kind") && node.kind == "get", 109 | isSetter: node.hasOwnProperty("kind") && node.kind == "set", 110 | isGenerator: node.value.generator 111 | }); 112 | if (_iterator["return"]) _iterator["return"](); 113 | break; 114 | } 115 | 116 | position = node.end; 117 | } 118 | } catch (err) { 119 | _didIteratorError = true; 120 | _iteratorError = err; 121 | } finally { 122 | try { 123 | if (!_iteratorNormalCompletion && _iterator["return"]) { 124 | _iterator["return"](); 125 | } 126 | } finally { 127 | if (_didIteratorError) { 128 | throw _iteratorError; 129 | } 130 | } 131 | } 132 | 133 | return result; 134 | }, 135 | writable: true, 136 | configurable: true 137 | }, 138 | getComments: { 139 | value: function getComments(prevEnd, nextStart) { 140 | if (!this.currentComment.done && prevEnd <= this.currentComment.value.start && nextStart > this.currentComment.value.end) { 141 | var comments = doctrine.parse(this.currentComment.value.text, { 142 | unwrap: true, 143 | recoverable: true 144 | }); 145 | 146 | comments.parsed = markdown.markdown.toHTML(comments.description); 147 | 148 | this.currentComment = this.comments.next(); 149 | 150 | return comments; 151 | } else { 152 | return null; 153 | } 154 | }, 155 | writable: true, 156 | configurable: true 157 | } 158 | }); 159 | 160 | return JavascriptParser; 161 | })(); 162 | 163 | module.exports = JavascriptParser; -------------------------------------------------------------------------------- /input/controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the base controller in TungstenJS. A defaults property is provided to be overridden and merged allowing you to define common selectors, classes, and strings that will be referenced throughout your controller. 3 | * 4 | * You can also bind element events to controller functions by defining a new entry in the listeners property. See the demo application for an example implementation. 5 | * 6 | * The event binding system and other elements of functionality are inspired by [JavascriptMVC's](http://javascriptmvc.com/) [implementation of the controller](http://javascriptmvc.com/docs/can.Control.html). 7 | 8 | * @class Controller 9 | * @author Andrew Odri andrew@affirmix.com 10 | */ 11 | export class Controller { 12 | /** 13 | * This returns a reference that whatever the top-most sub-class is, which comes in handy when managing instances in static functions on classes that are designed to be extended. 14 | * 15 | * @static 16 | * @property {Class} classReference Reference to the current class 17 | */ 18 | static get classReference() { return eval(this.name); } 19 | 20 | /** 21 | * This returns a reference that whatever the top-most sub-class is, which comes in handy when managing instances in static functions on classes that are designed to be extended. 22 | * 23 | * @property {Class} classReference Reference to the current class 24 | */ 25 | get classReference() { return eval(this.constructor.name); } 26 | 27 | /** 28 | * Defines default properties that will be merged into each controller instance by default. 29 | * 30 | * Due to the lack of support for class properties in ECMAScript 6, properties have been defined in getters, which are then merged with thier super functions by the constructor. 31 | * 32 | * Below is an example of a standard override of defaults that merges with it's sub class: 33 | * 34 | * ``` 35 | * get defaults() { 36 | * return { 37 | * selectors : { 38 | * displayElement : '.display' 39 | * ,searchField : '.search-field' 40 | * ,searchButton : '.search-button' 41 | * } 42 | * ,classes : { 43 | * active : 'active' 44 | * ,selected : 'selected' 45 | * ,loading : 'loading' 46 | * } 47 | * }; 48 | * } 49 | * ``` 50 | * 51 | * These properties can be accessed straight from controller instance itself. For example, to access selectors.displayElement defined in the above example, you would reference it with `controllerInstance.selectors.displayElement`. See the demo application for an example implementation. 52 | * 53 | * @property {Object} defaults The properties of this object will be merged into the controller itself, providing default values for the controller 54 | */ 55 | get defaults() { return {}; } 56 | 57 | /** 58 | * Defines the listener object which will be parsed through and then bound during construction. See the demo application for an example implementation. 59 | * 60 | * Due to the lack of support for class properties in ECMAScript 6, properties have been defined in getters, which are then merged with thier super functions by the constructor. 61 | * 62 | * Below is an example of a standard override of defaults that merges with it's sub class: 63 | * 64 | * ``` 65 | * get listeners() { 66 | * return [ 67 | * { selector : '{selectors.searchButton} click', handler : this.example } 68 | * ,{ selector : 'div#identification.classification click', handler : this.example } 69 | * ,{ selector : 'click', handler : this.example } 70 | * ]; 71 | * } 72 | * ``` 73 | * 74 | * @todo In each listener object, separate the event from the selector and give it it's own event property 75 | * @property {Array} listeners An array of objects that represent DOM query selectors, events, and callback functions for DOM event binding on the controller 76 | */ 77 | get listeners() { return []; } 78 | 79 | /** 80 | * The first thing the contructor does is merge this.defaults and this.listeners with the values defined in each super class. "Private" variables are created for acess within the constructor. 81 | * 82 | * Then, the element property of the controller is set to a jQuery object of the context. This will provide access to the jQuery function easily, and allow for easy referencing through the controller. 83 | * 84 | * Next, the properties in default are also merged into the controller instance. 85 | * 86 | * The listeners property is then parsed, binding all events to the defined functions, and passing them the correct context. 87 | * 88 | * In non-native ECMAScript 6 browsers, the code is often processed after document ready and load events are fired, so if these have been missed, they are triggered again. 89 | * 90 | * After all the processing is complete, the initialize function is called. This allows sub classes to perform thier own initialization after the core controller initialization has taken place in the constructor without having to explicitly call the constructor's. See the demo application for an example implementation. 91 | * 92 | * @todo Investigate whether re-triggering ready and load events will cause issues 93 | * @param {DOMElement} context Element that the controller will be bound to and use as it's scope 94 | * @returns {Controller} Instance of the controller object 95 | */ 96 | constructor(context) { 97 | console.log('controller.constructor()'); 98 | 99 | this.__defaults__ = this.defaults; 100 | this.__listeners__ = this.listeners; 101 | 102 | // What is this black magic? Ghetto introspection baby... We are basically taking the ugly out of each getter and putting into one lump of super-ugly 103 | var classIterator = this.constructor.__proto__; 104 | 105 | // Check if the prototype is defined; if it is empty then super class is now Object and we can't go further 106 | while(classIterator.hasOwnProperty('prototype')){ 107 | //while(Reflect.has(classIterator, 'name') && Reflect.get(classIterator, 'name') !== 'Empty'){ 108 | if(classIterator.hasOwnProperty('defaults')){ 109 | //if(Reflect.has(classIterator, 'defaults')){ 110 | this.__defaults__ = Object.assign(classIterator.prototype.defaults, this.__defaults__); 111 | } 112 | 113 | if(classIterator.hasOwnProperty('listeners')){ 114 | //if(Reflect.has(classIterator, 'listeners')){ 115 | this.__listeners__ = classIterator.prototype.listeners.concat(this.__listeners__); 116 | } 117 | 118 | // We are going down the rabbit hole now... Try and access the current classIterator's super class... 119 | classIterator = classIterator.__proto__; 120 | } 121 | 122 | // Rename context to element, and element to targetElement... 123 | this.element = $(context); 124 | 125 | // Merge defaults into the instance of this class... (I don't know if this is a good idea yet) 126 | Object.assign(this, this.__defaults__); 127 | 128 | for(let listener of this.__listeners__){ 129 | // Set all the main variables through destructured assignment... 130 | let [, objectString, selectorElement, event] = /(?:\{([^\{\}\s]*)\})*(\S+)*?\s*?(\S+)/.exec(listener.selector); 131 | 132 | // Break apart the object string and retrieve the reference... 133 | if(objectString){ 134 | let objectProperties = objectString.split('.'); 135 | selectorElement = this.defaults; 136 | 137 | while(objectProperties.length >= 1){ 138 | selectorElement = selectorElement[objectProperties.shift()]; 139 | } 140 | } 141 | 142 | // If no element was set, then assume it is the parent element... 143 | if(!selectorElement){ 144 | selectorElement = context 145 | } 146 | 147 | // Try and make this jQuery independant at some point... 148 | if($(selectorElement).is(context)){ 149 | $(selectorElement).on(event, $.proxy(listener.handler, this)); 150 | }else{ 151 | $(selectorElement, $(context)).on(event, $.proxy(listener.handler, this)); 152 | } 153 | 154 | // Non-native ES6 implementations only parse code after document ready and load events, so we need to re-trigger them... 155 | if($(selectorElement).is(document) && event == 'ready' && /interactive|complete/.test(document.readyState)){ 156 | $(selectorElement).trigger('ready'); 157 | } 158 | if($(selectorElement).is(document) && event == 'load' && /complete/.test(document.readyState)){ 159 | $(selectorElement).trigger('load'); 160 | } 161 | 162 | //console.log(selectorElement, event, listener.handler, document.readyState); 163 | } 164 | 165 | this.initialize(context); 166 | } 167 | 168 | /** 169 | * This function is designed to be overriden, allowing sub classes to perform thier own initialization after the core controller initialization has taken place in the constructor without having to explicitly call the constructor. 170 | * 171 | * @param {DOMElement} element Element that the constructor object uses for scoping 172 | * @param {Object} source Element that the constructor object uses for scoping 173 | */ 174 | initialize(element, source) { 175 | console.log('controller.initialize()'); 176 | } 177 | 178 | /** 179 | * This function destroys the instance of the controller, as well as the DOM element if defined. 180 | * 181 | * @param {Boolean} isIncludeElement Defines whether the DOM element that the controller is attached to should also be destroyed. 182 | */ 183 | destroy(isIncludeElement = false) { 184 | console.log('controller.destroy()'); 185 | 186 | if(isIncludeElement){ 187 | this.element.remove(); 188 | } 189 | 190 | delete this; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /dist/markdowntransformer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; }; 4 | 5 | var _slicedToArray = function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; for (var _iterator = arr[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) { _arr.push(_step.value); if (i && _arr.length === i) break; } return _arr; } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; 6 | 7 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; 8 | 9 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 10 | 11 | var fs = _interopRequire(require("fs")); 12 | 13 | var util = _interopRequire(require("util")); 14 | 15 | var MarkdownTransformer = (function () { 16 | function MarkdownTransformer() { 17 | _classCallCheck(this, MarkdownTransformer); 18 | } 19 | 20 | _prototypeProperties(MarkdownTransformer, { 21 | transform: { 22 | value: function transform(files) { 23 | var markdownFiles = new Map(); 24 | 25 | var navigation = MarkdownTransformer.buildNavigation(files); 26 | 27 | var _iteratorNormalCompletion = true; 28 | var _didIteratorError = false; 29 | var _iteratorError = undefined; 30 | 31 | try { 32 | for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 33 | var _step$value = _slicedToArray(_step.value, 2); 34 | 35 | var path = _step$value[0]; 36 | var file = _step$value[1]; 37 | var _iteratorNormalCompletion2 = true; 38 | var _didIteratorError2 = false; 39 | var _iteratorError2 = undefined; 40 | 41 | try { 42 | for (var _iterator2 = file.classes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 43 | var classObject = _step2.value; 44 | 45 | markdownFiles.set(classObject.name.toLowerCase() + ".md", navigation + MarkdownTransformer.buildClass(classObject)); 46 | } 47 | } catch (err) { 48 | _didIteratorError2 = true; 49 | _iteratorError2 = err; 50 | } finally { 51 | try { 52 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 53 | _iterator2["return"](); 54 | } 55 | } finally { 56 | if (_didIteratorError2) { 57 | throw _iteratorError2; 58 | } 59 | } 60 | } 61 | } 62 | } catch (err) { 63 | _didIteratorError = true; 64 | _iteratorError = err; 65 | } finally { 66 | try { 67 | if (!_iteratorNormalCompletion && _iterator["return"]) { 68 | _iterator["return"](); 69 | } 70 | } finally { 71 | if (_didIteratorError) { 72 | throw _iteratorError; 73 | } 74 | } 75 | } 76 | 77 | return markdownFiles; 78 | }, 79 | writable: true, 80 | configurable: true 81 | }, 82 | buildNavigation: { 83 | value: function buildNavigation(files) { 84 | var result = ""; 85 | 86 | var classHeading = "* %s\n"; 87 | var methodWrapper = " * %s(%s)\n"; 88 | var parameter = "[%s](%s)"; 89 | 90 | var _iteratorNormalCompletion = true; 91 | var _didIteratorError = false; 92 | var _iteratorError = undefined; 93 | 94 | try { 95 | for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 96 | var _step$value = _slicedToArray(_step.value, 2); 97 | 98 | var path = _step$value[0]; 99 | var file = _step$value[1]; 100 | var _iteratorNormalCompletion2 = true; 101 | var _didIteratorError2 = false; 102 | var _iteratorError2 = undefined; 103 | 104 | try { 105 | for (var _iterator2 = file.classes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 106 | var classObject = _step2.value; 107 | 108 | result += util.format(classHeading, classObject.name); 109 | 110 | var _iteratorNormalCompletion3 = true; 111 | var _didIteratorError3 = false; 112 | var _iteratorError3 = undefined; 113 | 114 | try { 115 | for (var _iterator3 = classObject.methods[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 116 | var method = _step3.value; 117 | 118 | var params = ""; 119 | var _iteratorNormalCompletion4 = true; 120 | var _didIteratorError4 = false; 121 | var _iteratorError4 = undefined; 122 | 123 | try { 124 | for (var _iterator4 = method.comments.tags[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 125 | var tag = _step4.value; 126 | 127 | if (tag.title == "param") { 128 | params += util.format(parameter, tag.name, ""); 129 | } 130 | } 131 | } catch (err) { 132 | _didIteratorError4 = true; 133 | _iteratorError4 = err; 134 | } finally { 135 | try { 136 | if (!_iteratorNormalCompletion4 && _iterator4["return"]) { 137 | _iterator4["return"](); 138 | } 139 | } finally { 140 | if (_didIteratorError4) { 141 | throw _iteratorError4; 142 | } 143 | } 144 | } 145 | 146 | result += util.format(methodWrapper, method.name, params); 147 | } 148 | } catch (err) { 149 | _didIteratorError3 = true; 150 | _iteratorError3 = err; 151 | } finally { 152 | try { 153 | if (!_iteratorNormalCompletion3 && _iterator3["return"]) { 154 | _iterator3["return"](); 155 | } 156 | } finally { 157 | if (_didIteratorError3) { 158 | throw _iteratorError3; 159 | } 160 | } 161 | } 162 | 163 | result += "\n"; 164 | } 165 | } catch (err) { 166 | _didIteratorError2 = true; 167 | _iteratorError2 = err; 168 | } finally { 169 | try { 170 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 171 | _iterator2["return"](); 172 | } 173 | } finally { 174 | if (_didIteratorError2) { 175 | throw _iteratorError2; 176 | } 177 | } 178 | } 179 | } 180 | } catch (err) { 181 | _didIteratorError = true; 182 | _iteratorError = err; 183 | } finally { 184 | try { 185 | if (!_iteratorNormalCompletion && _iterator["return"]) { 186 | _iterator["return"](); 187 | } 188 | } finally { 189 | if (_didIteratorError) { 190 | throw _iteratorError; 191 | } 192 | } 193 | } 194 | 195 | return result; 196 | }, 197 | writable: true, 198 | configurable: true 199 | }, 200 | buildClass: { 201 | value: function buildClass(classObject) { 202 | var result = ""; 203 | 204 | var classHeading = "# %s\n\n"; 205 | var methodHeading = "## %s\n\n"; 206 | var paragraph = "%s\n\n"; 207 | var tableWrapper = "%s
\n\n"; 208 | var parameter = "%s%s%s%s"; 209 | 210 | result += util.format(classHeading, classObject.name); 211 | result += util.format(paragraph, classObject.comments.description); 212 | 213 | var _iteratorNormalCompletion = true; 214 | var _didIteratorError = false; 215 | var _iteratorError = undefined; 216 | 217 | try { 218 | for (var _iterator = classObject.methods[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 219 | var method = _step.value; 220 | 221 | result += util.format(methodHeading, method.name); 222 | result += util.format(paragraph, method.comments.description); 223 | 224 | var table = ""; 225 | var _iteratorNormalCompletion2 = true; 226 | var _didIteratorError2 = false; 227 | var _iteratorError2 = undefined; 228 | 229 | try { 230 | for (var _iterator2 = method.comments.tags[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 231 | var tag = _step2.value; 232 | 233 | table += util.format(parameter, tag.title, tag.name, tag.type ? tag.type.name : "", tag.description); 234 | } 235 | } catch (err) { 236 | _didIteratorError2 = true; 237 | _iteratorError2 = err; 238 | } finally { 239 | try { 240 | if (!_iteratorNormalCompletion2 && _iterator2["return"]) { 241 | _iterator2["return"](); 242 | } 243 | } finally { 244 | if (_didIteratorError2) { 245 | throw _iteratorError2; 246 | } 247 | } 248 | } 249 | 250 | result += util.format(tableWrapper, table); 251 | } 252 | } catch (err) { 253 | _didIteratorError = true; 254 | _iteratorError = err; 255 | } finally { 256 | try { 257 | if (!_iteratorNormalCompletion && _iterator["return"]) { 258 | _iterator["return"](); 259 | } 260 | } finally { 261 | if (_didIteratorError) { 262 | throw _iteratorError; 263 | } 264 | } 265 | } 266 | 267 | return result; 268 | }, 269 | writable: true, 270 | configurable: true 271 | } 272 | }); 273 | 274 | return MarkdownTransformer; 275 | })(); 276 | 277 | module.exports = MarkdownTransformer; -------------------------------------------------------------------------------- /input/model.js: -------------------------------------------------------------------------------- 1 | import {Utility} from './utility'; 2 | 3 | /** 4 | * This class is designed to be an abstract model class. RESTful JSON services are also natively supported with full functionality easily enabled by overriding the services getter. 5 | * 6 | * Custom models can be defined by extending this class and overriding any appropriate properties and functions that you wish to customize. See the demo application for an example implementation. 7 | * 8 | * In the case of most RESTful APIs, you will only need to override the `services` property and the `filter` function. 9 | * 10 | * The naming conventions and structure for the majority of the functions and methods provided are inspired by [Laravel's](http://laravel.com/) [Eloquent ORM Model](http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Model.html). 11 | * 12 | * @class Model 13 | * @author Andrew Odri andrew@affirmix.com 14 | */ 15 | export class Model { 16 | /** 17 | * This returns a reference that whatever the top-most sub-class is, which comes in handy when managing instances in static functions on classes that are designed to be extended. 18 | * 19 | * @static 20 | * @property {Class} classReference Reference to the current class 21 | */ 22 | static get classReference() { return eval(this.name); } 23 | 24 | /** 25 | * This returns a reference that whatever the top-most sub-class is, which comes in handy when managing instances in static functions on classes that are designed to be extended. 26 | * 27 | * @property {Class} classReference Reference to the current class 28 | */ 29 | get classReference() { return eval(this.constructor.name); } 30 | 31 | /** 32 | * Templated URLs are supported using ECMAScript 6 quasi-literal tenplate syntax. See the MDN documentation for more information. See the demo application for an example implementation. 33 | * 34 | * Due to the lack of support for class properties in ECMAScript 6, properties have been defined in getters that merge with thier super functions. 35 | * 36 | * Below is an example of a standard override of services that merges with it's sub class: 37 | * 38 | * ``` 39 | * static get services() { 40 | * return super.services.merge({ 41 | * create : { method : 'POST', uri : 'https://itunes.apple.com/search?term=${term}', format : 'jsonp' } 42 | * ,find : { method : 'GET', uri : 'https://itunes.apple.com/search?term=${term}', format : 'jsonp' } 43 | * ,update : { method : 'PUT', uri : 'https://itunes.apple.com/search?term=${term}', format : 'jsonp' } 44 | * ,delete : { method : 'DELETE', uri : 'https://itunes.apple.com/search?term=${term}', format : 'jsonp' } 45 | * }); 46 | * } 47 | * ``` 48 | * 49 | * @static 50 | * @property {Object} services Object containing create, find, update, and delete properties that define RESTful service endpoints 51 | */ 52 | static get services() { 53 | return { 54 | create : { method : 'POST', uri : '', format : 'jsonp' } 55 | ,find : { method : 'GET', uri : '', format : 'jsonp' } 56 | ,update : { method : 'PUT', uri : '', format : 'jsonp' } 57 | ,delete : { method : 'DELETE', uri : '', format : 'jsonp' } 58 | } 59 | } 60 | 61 | /** 62 | * Creates a new instance of the model, and performs a save operation. 63 | * 64 | * @static 65 | * @param {Object} attributes Attributes that will be used to hydrate a new model instance. See `hydrate` for more information 66 | * @returns {Model} Deferred instance of the newly created model 67 | */ 68 | static create(attributes){ 69 | console.log('Model.create()'); 70 | 71 | return this.hydrate(attributes).save(); 72 | } 73 | 74 | /** 75 | * Finds and returns any available instances of the model. 76 | * 77 | * @static 78 | * @param {Object} attributes An object containing properties that correspond to the attributes in the templated RESTful URL, if not overriden by custom functionality 79 | * @returns {Array|Model} Deferred instance of the Models that are found 80 | */ 81 | static find(attributes) { 82 | console.log('Model.find()'); 83 | 84 | return this.__find__(attributes); 85 | } 86 | 87 | /** 88 | * Finds and returns once or more instances of the model or throws and error if none are found. 89 | * 90 | * @static 91 | * @param {Object} attributes An object containing properties that correspond to the attributes in the templated RESTful URL, if not overriden by custom functionality 92 | * @returns {Array|Model} Deferred instance of the models that are found 93 | */ 94 | static findOrFail(attributes) { 95 | console.log('Model.findOrFail()'); 96 | 97 | $.when( 98 | this.__find__(attributes) 99 | ).done(function(data){ 100 | if((data instanceof Object && data != {}) || (data instanceof Array && data != [])){ 101 | return data; 102 | }else{ 103 | throw new Error('Could not find instance of Model in class Model'); 104 | } 105 | }); 106 | } 107 | 108 | /** 109 | * Finds and returns once or more instances of the model or creates a new instance if none are found. 110 | * 111 | * @static 112 | * @param {Object} attributes An object containing properties that correspond to the attributes in the templated RESTful URL, if not overriden by custom functionality 113 | * @returns {Array|Model} Deferred instance of the Models that are found 114 | */ 115 | static findOrNew(attributes) { 116 | console.log('Model.findOrNew()'); 117 | 118 | $.when( 119 | this.__find__(attributes) 120 | ).done(function(data){ 121 | if((data instanceof Object && data != {}) || (data instanceof Array && data != [])){ 122 | return data; 123 | }else{ 124 | return this.hydrate(attributes); 125 | } 126 | }); 127 | } 128 | 129 | /** 130 | * Finds and returns the first instance of the model or creates a new instance and saves it if none are found. 131 | * 132 | * @static 133 | * @param {Object} attributes An object containing properties that correspond to the attributes in the templated RESTful URL, if not overriden by custom functionality 134 | * @returns {Array|Model} Deferred instance of the Models that are found 135 | */ 136 | static firstOrCreate(attributes) { 137 | console.log('Model.firstOrCreate()'); 138 | 139 | $.when( 140 | this.__find__(attributes, true) 141 | ).done(function(data){ 142 | if(data instanceof Object && data != {}){ 143 | return data; 144 | }else if(data instanceof Array && data != []){ 145 | return data[0]; 146 | }else{ 147 | return this.hydrate(attributes).save(); 148 | } 149 | }); 150 | } 151 | 152 | /** 153 | * Finds and returns the first instance of the model or creates a new instance if none are found. 154 | * 155 | * @static 156 | * @param {Object} attributes An object containing properties that correspond to the attributes in the templated RESTful URL, if not overriden by custom functionality 157 | * @returns {Array|Model} Deferred instance of the Models that are found 158 | */ 159 | static firstOrNew(attributes) { 160 | console.log('Model.firstOrNew()'); 161 | 162 | $.when( 163 | this.__find__(attributes, true) 164 | ).done(function(data){ 165 | if(data instanceof Object && data != {}){ 166 | return data; 167 | }else if(data instanceof Array && data != []){ 168 | return data[0]; 169 | }else{ 170 | return this.hydrate(attributes); 171 | } 172 | }); 173 | } 174 | 175 | /** 176 | * Create or update a model matching the attributes, and fill it with values. 177 | * 178 | * @static 179 | * @param {Object} attributes Attributes used to return the models that need to be updated 180 | * @param {Object} properties Properties that model instances will be updated with 181 | * @returns {Array} Deferred instance of the updated or created model(s) 182 | */ 183 | static updateOrCreate(attributes, properties){ 184 | console.log('Model.updateOrCreate()'); 185 | 186 | $.when( 187 | this.__find__(attributes, false, properties) 188 | ).done(function(data){ 189 | if((data instanceof Object && data != {} || data instanceof Array && data != [])){ 190 | return data; 191 | }else{ 192 | return this.hydrate(Object.assign(attributes, properties)).save(); 193 | } 194 | }); 195 | } 196 | 197 | /** 198 | * Destroys the model instances provided, and performs a delete operation. 199 | * 200 | * @static 201 | * @param {Array} items Model instances to be deleted 202 | * @returns {Boolean} Boolean expressing whether the operation was successful 203 | */ 204 | static destroy(items){ 205 | console.log('Model.destroy()'); 206 | 207 | var deferred = $.Deferred(); 208 | 209 | // Handle the parameteres and construct uri based on destructured assignment 210 | 211 | $.when( 212 | $.ajax({ 213 | type : this.services.delete.method 214 | ,url : Utility.stringFormat(this.services.delete.uri, attributes) 215 | ,dataType : this.services.delete.format 216 | }) 217 | ).done( 218 | (data, textStatus, jqXHR) => deferred.resolve( 219 | this.hydrate( 220 | this.filter(data) 221 | ) 222 | ) 223 | ); 224 | 225 | return deferred.promise(); 226 | } 227 | 228 | /** 229 | * Designed to be overridden, this allows raw data returned from a RESTful `find` method to be be processed before being sent `hydrate` for instantiation. See the demo application for an example implementation. 230 | * 231 | * @static 232 | * @param {*} data Data returned by the find function 233 | * @returns {Array|Object} Processed data from the find function 234 | */ 235 | static filter(data) { 236 | console.log('Model.filter()'); 237 | 238 | return data; 239 | } 240 | 241 | /** 242 | * Instantiates a model/models based on the data provided. This is called immediately after `filter`. 243 | * 244 | * @static 245 | * @param {Array|Object} data Data needed to instantiate new model instances 246 | * @returns {Array|Model} Newly instantiated model/models 247 | */ 248 | static hydrate(data) { 249 | console.log('Model.hydrate()'); 250 | 251 | let result; 252 | 253 | if(data instanceof Array){ 254 | result = []; 255 | 256 | for(let item of data){ 257 | if(item instanceof Object){ 258 | result.push(new this.classReference(item)); 259 | } 260 | } 261 | }else if(data instanceof Object){ 262 | result = new this.classReference(data); 263 | }else{ 264 | throw new Error('Cannot hydrate model from a non-object in class Model'); 265 | } 266 | 267 | return result; 268 | } 269 | 270 | /** 271 | * Internal function that finds and returns any available instances of the model. 272 | * 273 | * @static 274 | * @param {Object} attributes An object containing properties that correspond to the attributes in the templated RESTful URL, if not overriden by custom functionality 275 | * @param {Boolean} isSingle An optional boolean that defines whether one or many models are returned. Defaults to false. 276 | * @param {Object} properties An optional object containing properties that need to be be updated in the model(s) returned. Defaults to {}. 277 | * @returns {Array|Model} Deferred instance of the Models that are found 278 | */ 279 | static __find__(attributes, isSingle = false, properties = {}) { 280 | console.log('Model.__find__()'); 281 | 282 | var deferred = $.Deferred(); 283 | 284 | $.when( 285 | $.ajax({ 286 | type : this.services.find.method 287 | ,url : Utility.stringFormat(this.services.find.uri, attributes) 288 | ,dataType : this.services.find.format 289 | }) 290 | ).done( 291 | (data, textStatus, jqXHR) => { 292 | let filtered = this.filter(data); 293 | let whittled = ((isSingle || filtered.length == 1) && filtered instanceof Array) ? filtered[0] : filtered; 294 | let merged = Object.assign(whittled, properties); 295 | 296 | deferred.resolve( 297 | this.hydrate(merged) 298 | ); 299 | } 300 | ); 301 | 302 | return deferred.promise(); 303 | } 304 | 305 | /** 306 | * Constructs the model instance by merging the attributes with the new model instance. 307 | * 308 | * @constructor 309 | * @param {Object} attributes Attributes needed to instantiate new model instances 310 | * @returns {Model} Instance of the model 311 | */ 312 | constructor(attributes) { 313 | console.log('model.constructor()'); 314 | 315 | Object.assign(this, attributes); 316 | } 317 | 318 | /** 319 | * Updates the model, and performs an update operation. 320 | * 321 | * @param {Object} attributes Attributes that will be used to hydrate a new model instance. See `hydrate` for more information. 322 | * @returns {Model} Deferred instance of the newly created model 323 | */ 324 | update(attributes){ 325 | console.log('model.update()'); 326 | 327 | Object.assign(this, attributes).save(); 328 | } 329 | 330 | /** 331 | * Performs a save operation. 332 | * 333 | * @returns {Model} Instance of the saved model 334 | */ 335 | save() { 336 | console.log('model.save()'); 337 | 338 | var deferred = $.Deferred(); 339 | 340 | // May need to implement a check to see whether create or save would be better... 341 | 342 | $.when( 343 | $.ajax({ 344 | type : this.services.update.method 345 | ,url : Utility.stringFormat(this.services.update.uri, attributes) 346 | ,dataType : this.services.update.format 347 | }) 348 | ).done( 349 | (data, textStatus, jqXHR) => deferred.resolve( 350 | this 351 | ) 352 | ); 353 | 354 | return deferred.promise(); 355 | } 356 | 357 | /** 358 | * Deletes the instance of the model. 359 | * 360 | * @returns {Boolean} Returns a deferred boolean indiciation whether the model was delete sucessfully 361 | */ 362 | delete() { 363 | console.log('model.delete()'); 364 | 365 | var deferred = $.Deferred(); 366 | 367 | $.when( 368 | $.ajax({ 369 | type : this.services.delete.method 370 | ,url : Utility.stringFormat(this.services.delete.uri, attributes) 371 | ,dataType : this.services.delete.format 372 | }) 373 | ).done( 374 | (data, textStatus, jqXHR) => deferred.resolve( 375 | true 376 | ) 377 | ); 378 | 379 | return deferred.promise(); 380 | } 381 | } 382 | --------------------------------------------------------------------------------