From ce02d3402f55f408a872dd4b8b8ad107ca6601a0 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 3 Nov 2014 11:05:00 -0800 Subject: [PATCH] PIXI-in-Docs - Initial - Initial support for generating PIXI-combined documentation - Includes yuidoc-to-jsdoc for generating pixi-jsdoc.js - Creates doc (using pixidoc + builddoc) tasks - Adds sourceproxy JSDoc plugin to map in corrected file/line meta - Added yuidocjs as a dev-dependency --- docs/build/conf.json | 2 + docs/build/local-plugins/sourceproxy.js | 38 +++ package.json | 5 +- tasks/builddoc.js | 30 +++ tasks/pixidoc.js | 58 +++++ tasks/yuidoc-to-jsdoc/converter.js | 300 ++++++++++++++++++++++++ 6 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 docs/build/local-plugins/sourceproxy.js create mode 100644 tasks/builddoc.js create mode 100644 tasks/pixidoc.js create mode 100644 tasks/yuidoc-to-jsdoc/converter.js diff --git a/docs/build/conf.json b/docs/build/conf.json index b4c4dddc6..02a235b6d 100644 --- a/docs/build/conf.json +++ b/docs/build/conf.json @@ -4,6 +4,7 @@ }, "source": { "include": [ + "../pixi-jsdoc.js", "../../src/Phaser.js", "../../src/animation/", "../../src/core/", @@ -30,6 +31,7 @@ }, "plugins" : [ "local-plugins/proptomember", + "local-plugins/sourceproxy", "plugins/markdown" ], "templates": { diff --git a/docs/build/local-plugins/sourceproxy.js b/docs/build/local-plugins/sourceproxy.js new file mode 100644 index 000000000..b33407655 --- /dev/null +++ b/docs/build/local-plugins/sourceproxy.js @@ -0,0 +1,38 @@ +/** +* For use with custom `@sourcepath`, `@sourceline`, `@nosource` properties +* (which are used in YUIDoc-to-JSDoc to supply source documentation) +*/ + +var path = require('path'); + +exports.defineTags = function(dictionary) { + + dictionary.defineTag('nosource', { + onTagged: function (doclet, tag) { + doclet.meta.nosource = true; + //doclet.meta.path = ''; + //doclet.meta.filename = ''; + } + }); + + dictionary.defineTag('sourcefile', { + onTagged: function (doclet, tag) { + var filename = tag.value; + doclet.meta.path = path.dirname(filename); + doclet.meta.filename = path.basename(filename); + } + }); + + dictionary.defineTag('sourceline', { + onTagged: function (doclet, tag) { + var lineno = tag.value; + doclet.meta.lineno = lineno; + } + }); + +} + +exports.handlers = {}; +exports.handlers.newDoclet = function (e) { + +}; \ No newline at end of file diff --git a/package.json b/package.json index 2d5166271..04a67483d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "grunt-contrib-uglify": "^0.4.0", "grunt-notify": "^0.3.0", "grunt-text-replace": "^0.3.11", - "load-grunt-config": "~0.7.2" + "load-grunt-config": "~0.7.2", + "yuidocjs": "^0.3.50" } -} \ No newline at end of file +} diff --git a/tasks/builddoc.js b/tasks/builddoc.js new file mode 100644 index 000000000..2c13bf551 --- /dev/null +++ b/tasks/builddoc.js @@ -0,0 +1,30 @@ +/** +* A quick stub-task for running generating the JSDocs. +* This should probably be migrated to use grunt-jsdoc@beta (for jsdoc 3.x) or similar. +*/ +'use strict'; + +module.exports = function (grunt) { + + grunt.registerTask('builddoc', 'Builds the project documentation', function () { + + var done = this.async(); + + grunt.util.spawn({ + cmd: 'jsdoc', + args: ['-c', 'conf.json', '../../README.md'], + opts: { + cwd: 'docs/build' + } + }, function (error, result, code) { + if (error) { + grunt.fail.warn("" + result); + done(false); + } else { + done(); + } + }); + + }); + +}; \ No newline at end of file diff --git a/tasks/pixidoc.js b/tasks/pixidoc.js new file mode 100644 index 000000000..8c3356c7b --- /dev/null +++ b/tasks/pixidoc.js @@ -0,0 +1,58 @@ +/** +* Generates the appropriate JSDoc from some (PIXI) YUIDoc. +* This could be turned into a more general pacakge. +* +* Requires: yuidocjs +*/ +'use strict'; + +function generateYuiDoc (sourcePaths, grunt) { + + var Y = require('yuidocjs'); + + var options = { + parseOnly: true, + quiet: true, + paths: sourcePaths + }; + + return (new Y.YUIDoc(options)).run(); +} + +module.exports = function (grunt) { + + grunt.registerTask('pixidoc', 'Generates JSDoc from the PIXI YUIdocs', function () { + + var sources = ['C:/code/ph/phaser/src/pixi']; + var output = 'docs/pixi-jsdoc.js'; + + var yui2jsdoc = require('./yuidoc-to-jsdoc/converter'); + var fs = require('fs'); + var path = require('path'); + + // Right now yuidocsjs requires an absolute path so it emits an + // absolute path in the jsdoc (or the JSDoc will error on missing files) + sources = sources.map(function (source) { + return path.resolve(source); + }); + + var data = generateYuiDoc(sources); + + if (!data) { + grunt.fail.warn("PIXI YUIDoc not generated - nothing to do") + return; + } + + // Fake in namespace (current limitation) + var header = + "/**\n" + + "* @namespace PIXI\n" + + "*/"; + + var res = yui2jsdoc.convert(data); + var flat = res.join("\n"); + fs.writeFileSync(output, header + "\n" + flat); + + }); + +}; \ No newline at end of file diff --git a/tasks/yuidoc-to-jsdoc/converter.js b/tasks/yuidoc-to-jsdoc/converter.js new file mode 100644 index 000000000..ca4971b86 --- /dev/null +++ b/tasks/yuidoc-to-jsdoc/converter.js @@ -0,0 +1,300 @@ +/** +* Converts the JSON data out of YUIDoc into JSDoc documentation. +* +* Use a JSDoc plugin to handle custom @sourcefile/@sourceline and reattach meta-data. +* +* This works on the current PIXI source code (and, unfortunately, exposes a few issues it has). +*/ +'use strict'; + +// Does not work with 'props' +function paramdesc_to_str(desc, typedescs) { + var name = desc.name; + var typename = desc.type; + var description = desc.description; + + if (desc.optional) { + if (desc.optdefault) { + name = "[" + name + "=" + desc.optdefault + "]"; + } else { + name = "[" + name + "]" + } + } + + return "{" + resolve_typename(typename, typedescs) + "} " + name + " - " + description; +} + +function returndesc_to_string(desc, typedescs) { + var typename = desc.type; + var description = desc.description; + if (typename) { + return "{" + resolve_typename(typename, typedescs) + "} " + description; + } else { + return description; + } +} + +/** +* Convert flat 'typeitems' found in YUIDoc to a dictionary: +* className: { +* items: [..] - only properties and methods +* } +*/ +function group_typeitems(typeitems) { + + var types = {}; + + typeitems.forEach(function (itemdesc, i) { + var class_name = itemdesc['class']; + var itemtype = itemdesc.itemtype; + + if (itemtype === 'method' || itemtype === 'property') + { + var type = types[class_name]; + if (!type) { + type = types[class_name] = { + items: [] + }; + } + + type.items.push(itemdesc); + } + }); + + return types; + +} + +// Use this for anything but trivial types; it takes apart complex types +function resolve_typename(typename, typedescs) { + + if (!typename) { typename = "Any"; } + + if (typename.indexOf('|') > -1) { + var typenames = typename.split(/[|]/g); + } else { + typenames = [typename]; + } + + typenames = typenames.map(function (part) { + + // YUIDoc is type... and JSDoc is ...type + var repeating = false; + if (part.match(/[.]{2,}/)) { + repeating = true; + part = part.replace(/[.]{2,}/g, ''); + } + + // This may happen for some terribly invalid input; ideally this would not be + // "handled" here, but trying to work with some not-correct input.. + part = part.replace(/[^a-zA-Z0-9_$<>.]/g, ''); + + var resolved = resolve_single_typename(part, typedescs); + if (repeating) { + return "..." + resolved + } else { + return resolved; + } + }); + + if (typenames.length > 1) { + return "(" + typenames.join("|") + ")"; + } else { + return typenames[0]; + } +} + +function resolve_single_typename(typename, typedescs) { + + if (!typename || typename.toLowerCase() == "Any" || typename === "*") { + return ""; // "Any" + } + + var typedesc = typedescs[typename]; + if (typedesc) { + return typedesc.module + "." + typename; + } else { + return typename; + } +} + +function resolve_item_name(name, typedesc, typedescs) { + var typename = resolve_single_typename(typedesc.name, typedescs); + return typename + "#" + name; +} + +function methoddesc_to_attrs(itemdesc, typedesc, typedescs) +{ + var attrs = []; + + if (itemdesc.description) { + attrs.push(['description', itemdesc.description]); + } + attrs.push(['method', resolve_item_name(itemdesc.name, typedesc, typedescs)]); + if (itemdesc.params) + { + itemdesc.params.forEach(function (param, i) { + attrs.push(['param', paramdesc_to_str(param, typedescs)]); + }); + } + + if (itemdesc['return']) + { + attrs.push(['return', returndesc_to_string(itemdesc['return'], typedescs)]); + } + + if (typedesc.file) { + attrs.push(['sourcefile', typedesc.file]); + attrs.push(['sourceline', typedesc.line]); + } + + return attrs; +} + +function propertydesc_to_attrs(itemdesc, typedesc, typedescs) +{ + var attrs = []; + + if (itemdesc.description) { + attrs.push(['description', itemdesc.description]); + } + attrs.push(['member', resolve_item_name(itemdesc.name, typedesc, typedescs)]); + attrs.push(['type', "{" + resolve_typename(itemdesc.type, typedescs) + "}"]); + + var access = itemdesc['access']; + if (access) { + attrs.push(['access', access]); + } + + if (itemdesc['readonly'] !== undefined) { + attrs.push(['readonly', '']); + } + + if (itemdesc['default'] !== undefined) { + attrs.push(['default', itemdesc['default']]); + } + + if (typedesc.file) { + attrs.push(['sourcefile', typedesc.file]); + attrs.push(['sourceline', typedesc.line]); + } + + return attrs; +} + +function write_attr_block (attrs, res) { + + if (attrs) { + res.push("/**"); + + attrs.forEach(function (attr) { + var name = attr[0]; + var value = attr[1]; + if (value !== undefined) { + res.push("* @" + name + " " + value); + } else { + res.push("* @" + name); + } + }); + + res.push("*/"); + } + +} + +function itemdesc_to_attrs(itemdesc, typedesc, typedescs) { + + if (itemdesc.itemtype === 'method') + { + return methoddesc_to_attrs(itemdesc, typedesc, typedescs); + } + else if (itemdesc.itemtype === 'property') + { + return propertydesc_to_attrs(itemdesc, typedesc, typedescs); + } + +} + +function typedesc_to_attrs (typedesc, typedescs) { + + var attrs = []; + + // Bug in PIXI (test) docs has a "static constructor", whoops! + if (typedescs.is_constructor || !typedescs['static']) { + + attrs.push(['class', resolve_single_typename(typedesc.name, typedescs)]); + + if (typedesc.description) { + attrs.push(['classdesc', typedesc.description]); + } + + } else { + // Not constructor, possibly static .. + + attrs.push(['description', typedesc.description]); + attrs.push(['namespace', resolve_single_typename(typedesc.name, typedescs)]); + + } + + var extendsname = typedesc['extends']; + if (extendsname) { + var extenddesc = typedescs[extendsname]; + if (extenddesc) { + attrs.push(['augments', resolve_single_typename(extendsname, typedescs)]); + } else { + attrs.push(['augments', extendsname]); + } + } + + if (typedesc.params) + { + typedesc.params.forEach(function (paramdesc, i) { + attrs.push(['param', paramdesc_to_str(paramdesc, typedescs)]); + }); + } + + if (typedesc.file) { + attrs.push(['sourcefile', typedesc.file]); + attrs.push(['sourceline', typedesc.line]); + } + + return attrs; + +} + +/** +* Convert a "data.json" (as JSON, not text) from YUIDoc to an equivalent JSDoc markup. +*/ +function yuidocDatatoJSDoc(data) { + + var typedescs = data.classes; + var type_itemdesc_groups = group_typeitems(data.classitems); + + var res = []; + + Object.keys(typedescs).forEach(function (name) { + var typedesc = typedescs[name]; + + var typeattrs = typedesc_to_attrs(typedesc, typedescs); + write_attr_block(typeattrs, res); + + var type_itemdesc = type_itemdesc_groups[name]; + if (type_itemdesc) { + type_itemdesc.items.forEach(function (itemdesc, i) { + var attrs = itemdesc_to_attrs(itemdesc, typedesc, typedescs); + write_attr_block(attrs, res); + }); + } else { + console.log("No items for " + name); + } + }); + + return res; + +}; + +exports.convert = function (yuidoc) { + + return yuidocDatatoJSDoc(yuidoc); + +};