change doc gen

This commit is contained in:
Amish Shah 2016-08-21 17:26:35 +01:00
parent 30e6bbd7a4
commit 985681f1f6
18 changed files with 697 additions and 168 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,166 +0,0 @@
let fs;
/* eslint no-console:0 no-return-assign:0 */
let parse;
const customDocs = require('../custom/index');
const GEN_VERSION = 10;
try {
fs = require('fs-extra');
parse = require('jsdoc-parse');
} catch (e) {
console.log('Error loading fs-extra or jsdoc-parse:');
console.log(e);
process.exit();
}
console.log('Starting...');
let json = '';
const stream = parse({
src: ['./src/*.js', './src/*/*.js', './src/**/*.js'],
private: true,
});
const cwd = (`${process.cwd()}\\`).replace(/\\/g, '/');
const regex = /([\w]+)([^\w]+)/;
const regexG = /([\w]+)([^\w]+)/g;
function matchReturnName(str) {
const matches = str.match(regexG);
const output = [];
if (matches) {
for (const match of matches) {
const groups = match.match(regex);
output.push([groups[1], groups[2]]);
}
} else {
output.push([str.match(/(\w+)/g), '']);
}
return output;
}
function cleanPaths() {
for (const item of json) {
if (item.meta && item.meta.path) {
item.meta.path = item.meta.path.replace(/\\/g, '/').replace(cwd, '');
}
}
}
function firstPass() {
const cleaned = {
classes: {},
interfaces: {},
typedefs: {},
};
for (const itemID in json) {
const item = json[itemID];
if (item.kind === 'class') {
delete json[itemID];
cleaned.classes[item.longname] = {
meta: item,
functions: [],
properties: [],
events: [],
};
} else if (item.kind === 'interface') {
delete json[itemID];
cleaned.interfaces[item.longname] = {
meta: item,
functions: [],
properties: [],
events: [],
};
}
}
return cleaned;
}
const seenEvents = {};
function clean() {
const cleaned = firstPass();
for (const item of json) {
if (!item) {
continue;
}
if (item.kind === 'member') {
const obj = cleaned.classes[item.memberof] || cleaned.interfaces[item.memberof];
const newTypes = [];
for (const name of item.type.names) {
newTypes.push(matchReturnName(name));
}
item.type = newTypes;
obj.properties.push(item);
} else if (item.kind === 'function' && item.memberof) {
const obj = cleaned.classes[item.memberof] || cleaned.interfaces[item.memberof];
const newReturns = [];
if (!item.returns) {
item.returns = [{
type: {
names: ['null'],
},
}];
}
for (const name of item.returns[0].type.names) {
newReturns.push(matchReturnName(name));
}
item.returns = newReturns;
obj.functions.push(item);
} else if (item.kind === 'typedef') {
cleaned.typedefs[item.longname] = item;
} else if (item.kind === 'constructor') {
const obj = cleaned.classes[item.memberof] || cleaned.interfaces[item.memberof];
obj.constructor = item;
} else if (item.kind === 'event') {
if (seenEvents[item.name]) {
console.log('dupe logs for', item.name);
}
seenEvents[item.name] = true;
const obj = cleaned.classes[item.memberof] || cleaned.interfaces[item.memberof];
if (item.params) {
for (const param of item.params) {
const newTypes = [];
for (const name of param.type.names) {
newTypes.push(matchReturnName(name));
}
param.type = newTypes;
}
}
item.params = [
{
type: item.params,
},
];
obj.events.push(item);
}
}
json = cleaned;
}
function next() {
json = JSON.parse(json);
cleanPaths();
console.log('parsed inline code');
clean();
json = {
meta: {
version: GEN_VERSION,
},
custom: customDocs,
json,
};
fs.writeFile('./docs/docs.json', JSON.stringify(json, null, 0), err => {
if (err) {
throw err;
}
console.log('done');
});
}
stream.on('data', chunk => json += chunk.toString('utf-8'));
stream.on('end', () => next());

View file

@ -0,0 +1,4 @@
{
"GEN_VERSION": 10,
"COMPRESS": false
}

View file

@ -0,0 +1,25 @@
/* eslint no-console:0 no-return-assign:0 */
const parse = require('jsdoc-parse');
module.exports = class DocumentationScanner {
constructor(generator) {
this.generator = generator;
}
scan(directory) {
return new Promise((resolve, reject) => {
const stream = parse({
src: [`${directory}*.js`, `${directory}**/*.js`],
private: true,
});
let json = '';
stream.on('data', chunk => json += chunk.toString('utf-8'));
stream.on('error', reject);
stream.on('end', () => {
json = JSON.parse(json);
resolve(json);
});
});
}
};

View file

@ -0,0 +1,111 @@
const DocumentedClass = require('./types/DocumentedClass');
const DocumentedInterface = require('./types/DocumentedInterface');
const DocumentedTypeDef = require('./types/DocumentedTypeDef');
const DocumentedConstructor = require('./types/DocumentedConstructor');
const DocumentedMember = require('./types/DocumentedMember');
const DocumentedFunction = require('./types/DocumentedFunction');
const DocumentedEvent = require('./types/DocumentedEvent');
const GEN_VERSION = require('./config.json').GEN_VERSION;
class Documentation {
constructor(items) {
this.classes = new Map();
this.interfaces = new Map();
this.typedefs = new Map();
this.parse(items);
}
registerRoots(data) {
for (const item of data) {
switch (item.kind) {
case 'class':
this.classes.set(item.name, new DocumentedClass(this, item));
break;
case 'interface':
this.interfaces.set(item.name, new DocumentedInterface(this, item));
break;
case 'typedef':
this.typedefs.set(item.name, new DocumentedTypeDef(this, item));
break;
default:
break;
}
}
}
findParent(item) {
if (['constructor', 'member', 'function', 'event'].indexOf(item.kind) > -1) {
if (this.classes.get(item.memberof)) {
return this.classes.get(item.memberof);
}
if (this.interfaces.get(item.memberof)) {
return this.interfaces.get(item.memberof);
}
}
return;
}
parse(items) {
this.registerRoots(
items.filter(
item => ['class', 'interface', 'typedef'].indexOf(item.kind) > -1
)
);
const members = items.filter(
item => ['class', 'interface', 'typedef'].indexOf(item.kind) === -1
);
const unknowns = new Map();
for (const member of members) {
let item;
switch (member.kind) {
case 'constructor':
item = new DocumentedConstructor(this, member);
break;
case 'member':
item = new DocumentedMember(this, member);
break;
case 'function':
item = new DocumentedFunction(this, member);
break;
case 'event':
item = new DocumentedEvent(this, member);
break;
default:
unknowns.set(member.kind, member);
break;
}
if (!item) {
continue;
}
const parent = this.findParent(member);
if (!parent) {
throw new Error(`${member.name} has no accessible parent - ${JSON.stringify(member)}`);
}
parent.add(item);
}
if (unknowns.size > 0) {
Array.from(unknowns.keys()).map(
k => console.log(`Unknown documentation kind ${k} - \n${JSON.stringify(unknowns.get(k))}\n`
));
}
}
serialize() {
const meta = {
version: GEN_VERSION,
date: Date.now(),
};
const serialized = {
meta,
classes: Array.from(this.classes.values()).map(c => c.serialize()),
interfaces: Array.from(this.interfaces.values()).map(i => i.serialize()),
typedefs: Array.from(this.typedefs.values()).map(t => t.serialize()),
};
return serialized;
}
}
module.exports = Documentation;

View file

@ -0,0 +1,26 @@
/* eslint no-console:0 no-return-assign:0 */
const GEN_VERSION = require('./config.json').GEN_VERSION;
const compress = require('./config.json').COMPRESS;
const DocumentationScanner = require('./doc-scanner');
const Documentation = require('./documentation');
const fs = require('fs-extra');
const zlib = require('zlib');
const docScanner = new DocumentationScanner(this);
function parseDocs(json) {
console.log(`${json.length} items found`);
const documentation = new Documentation(json);
let output = JSON.stringify(documentation.serialize(), null, 0);
if (compress) {
output = zlib.deflateSync(output).toString('utf8');
}
fs.writeFileSync('./docs/docs.json', output);
}
console.log(`using format version ${GEN_VERSION}`);
console.log('scanning for documentation');
docScanner.scan('./src/')
.then(parseDocs)
.catch(console.error);

View file

@ -0,0 +1,83 @@
const DocumentedItem = require('./DocumentedItem');
const DocumentedItemMeta = require('./DocumentedItemMeta');
const DocumentedConstructor = require('./DocumentedConstructor');
const DocumentedFunction = require('./DocumentedFunction');
const DocumentedMember = require('./DocumentedMember');
const DocumentedEvent = require('./DocumentedEvent');
/*
{ id: 'VoiceChannel',
longname: 'VoiceChannel',
name: 'VoiceChannel',
scope: 'global',
kind: 'class',
augments: [ 'GuildChannel' ],
description: 'Represents a Server Voice Channel on Discord.',
meta:
{ lineno: 7,
filename: 'VoiceChannel.js',
path: 'src/structures' },
order: 232 }
*/
class DocumentedClass extends DocumentedItem {
constructor(docParent, data) {
super(docParent, data);
this.props = new Map();
this.methods = new Map();
this.events = new Map();
}
add(item) {
if (item instanceof DocumentedConstructor) {
if (this.classConstructor) {
throw new Error(`Doc ${this.directData.name} already has constructor - ${this.directData.classConstructor}`);
}
this.classConstructor = item;
} else if (item instanceof DocumentedFunction) {
if (this.methods.get(item.directData.name)) {
throw new Error(`Doc ${this.directData.name} already has method ${item.name}`);
}
this.methods.set(item.name, item);
} else if (item instanceof DocumentedMember) {
if (this.props.get(item.directData.name)) {
throw new Error(`Doc ${this.directData.name} already has prop ${item.name}`);
}
this.props.set(item.name, item);
} else if (item instanceof DocumentedEvent) {
if (this.props.get(item.directData.name)) {
throw new Error(`Doc ${this.directData.name} already has event ${item.name}`);
}
this.events.set(item.name, item);
}
}
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
this.directData.meta = new DocumentedItemMeta(this, data.meta);
}
serialize() {
super.serialize();
const { id, name, description, meta, augments, access } = this.directData;
const serialized = {
id,
name,
description,
meta: meta.serialize(),
extends: augments,
access,
};
if (this.classConstructor) {
serialized.classConstructor = this.classConstructor.serialize();
}
serialized.methods = Array.from(this.methods.values()).map(m => m.serialize());
serialized.properties = Array.from(this.props.values()).map(p => p.serialize());
serialized.events = Array.from(this.events.values()).map(e => e.serialize());
return serialized;
}
}
module.exports = DocumentedClass;

View file

@ -0,0 +1,32 @@
const DocumentedItem = require('./DocumentedItem');
/*
{ id: 'Client()',
longname: 'Client',
name: 'Client',
kind: 'constructor',
description: 'Creates an instance of Client.',
memberof: 'Client',
params:
[ { type: [Object],
optional: true,
description: 'options to pass to the client',
name: 'options' } ],
order: 10 }
*/
class DocumentedConstructor extends DocumentedItem {
registerMetaInfo(data) {
this.directData = data;
}
serialize() {
super.serialize();
const { id, name, description, memberof, access } = this.directData;
return { id, name, description, memberof, access };
}
}
module.exports = DocumentedConstructor;

View file

@ -0,0 +1,72 @@
const DocumentedItem = require('./DocumentedItem');
const DocumentedItemMeta = require('./DocumentedItemMeta');
/*
{
"id":"Client#event:guildMemberRolesUpdate",
"longname":"Client#event:guildMemberRolesUpdate",
"name":"guildMemberRolesUpdate",
"scope":"instance",
"kind":"event",
"description":"Emitted whenever a Guild Member's Roles change - i.e. new role or removed role",
"memberof":"Client",
"params":[
{
"type":{
"names":[
"Guild"
]
},
"description":"the guild that the update affects",
"name":"guild"
},
{
"type":{
"names":[
"Array.<Role>"
]
},
"description":"the roles before the update",
"name":"oldRoles"
},
{
"type":{
"names":[
"Guild"
]
},
"description":"the roles after the update",
"name":"newRoles"
}
],
"meta":{
"lineno":91,
"filename":"Guild.js",
"path":"src/structures"
},
"order":110
}
*/
class DocumentedEvent extends DocumentedItem {
registerMetaInfo(data) {
this.directData = data;
this.directData.meta = new DocumentedItemMeta(this, data.meta);
}
serialize() {
super.serialize();
const { id, name, description, memberof, meta } = this.directData;
return {
id,
name,
description,
memberof,
meta: meta.serialize(),
};
}
}
module.exports = DocumentedEvent;

View file

@ -0,0 +1,91 @@
const DocumentedItem = require('./DocumentedItem');
const DocumentedItemMeta = require('./DocumentedItemMeta');
const DocumentedVarType = require('./DocumentedVarType');
const DocumentedParam = require('./DocumentedParam');
/*
{
"id":"ClientUser#sendTTSMessage",
"longname":"ClientUser#sendTTSMessage",
"name":"sendTTSMessage",
"scope":"instance",
"kind":"function",
"inherits":"User#sendTTSMessage",
"inherited":true,
"implements":[
"TextBasedChannel#sendTTSMessage"
],
"description":"Send a text-to-speech message to this channel",
"memberof":"ClientUser",
"params":[
{
"type":{
"names":[
"String"
]
},
"description":"the content to send",
"name":"content"
}
],
"examples":[
"// send a TTS message..."
],
"returns":[
{
"type":{
"names":[
"Promise.<Message>"
]
}
}
],
"meta":{
"lineno":38,
"filename":"TextBasedChannel.js",
"path":src/structures/interface"
},
"order":293
}
*/
class DocumentedFunction extends DocumentedItem {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
this.directData.meta = new DocumentedItemMeta(this, data.meta);
this.directData.returns = new DocumentedVarType(this, data.returns ? data.returns[0].type : {
names: ['null'],
});
const newParams = [];
for (const param of data.params) {
newParams.push(new DocumentedParam(this, param));
}
this.directData.params = newParams;
}
serialize() {
super.serialize();
const {
id, name, description, memberof, examples, inherits, inherited, meta, returns, params, access,
} = this.directData;
const serialized = {
id,
access,
name,
description,
memberof,
examples,
inherits,
inherited,
meta: meta.serialize(),
returns: returns.serialize(),
params: params.map(p => p.serialize()),
};
serialized.implements = this.directData.implements;
return serialized;
}
}
module.exports = DocumentedFunction;

View file

@ -0,0 +1,32 @@
const DocumentedClass = require('./DocumentedClass');
/*
{ id: 'TextBasedChannel',
longname: 'TextBasedChannel',
name: 'TextBasedChannel',
scope: 'global',
kind: 'interface',
classdesc: 'Interface for classes that have text-channel-like features',
params: [],
meta:
{ lineno: 5,
filename: 'TextBasedChannel.js',
path: 'src/structures/interface' },
order: 175 }
*/
class DocumentedInterface extends DocumentedClass {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
// this.directData.meta = new DocumentedItemMeta(this, data.meta);
}
serialize() {
const serialized = super.serialize();
serialized.description = this.directData.classdesc;
return serialized;
}
}
module.exports = DocumentedInterface;

View file

@ -0,0 +1,17 @@
class DocumentedItem {
constructor(parent, info) {
this.parent = parent;
this.directData = {};
this.registerMetaInfo(info);
}
registerMetaInfo() {
return;
}
serialize() {
return;
}
}
module.exports = DocumentedItem;

View file

@ -0,0 +1,29 @@
const cwd = (`${process.cwd()}\\`).replace(/\\/g, '/');
const backToForward = /\\/g;
const DocumentedItem = require('./DocumentedItem');
/*
{ lineno: 7,
filename: 'VoiceChannel.js',
path: 'src/structures' },
*/
class DocumentedItemMeta extends DocumentedItem {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData.line = data.lineno;
this.directData.file = data.filename;
this.directData.path = data.path.replace(backToForward, '/').replace(cwd, '');
}
serialize() {
super.serialize();
const { line, file, path } = this.directData;
return { line, file, path };
}
}
module.exports = DocumentedItemMeta;

View file

@ -0,0 +1,47 @@
const DocumentedItem = require('./DocumentedItem');
const DocumentedItemMeta = require('./DocumentedItemMeta');
const DocumentedVarType = require('./DocumentedVarType');
/*
{ id: 'Client#rest',
longname: 'Client#rest',
name: 'rest',
scope: 'instance',
kind: 'member',
description: 'The REST manager of the client',
memberof: 'Client',
type: { names: [ 'RESTManager' ] },
access: 'private',
meta:
{ lineno: 32,
filename: 'Client.js',
path: 'src/client' },
order: 11 }
*/
class DocumentedMember extends DocumentedItem {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
this.directData.meta = new DocumentedItemMeta(this, data.meta);
this.directData.type = new DocumentedVarType(this, data.type);
}
serialize() {
super.serialize();
const { id, name, description, memberof, type, access, meta } = this.directData;
return {
id,
name,
description,
memberof,
type: type.serialize(),
access,
meta: meta.serialize(),
};
}
}
module.exports = DocumentedMember;

View file

@ -0,0 +1,35 @@
const DocumentedItem = require('./DocumentedItem');
const DocumentedVarType = require('./DocumentedVarType');
/*
{
"type":{
"names":[
"Guild"
]
},
"description":"the roles after the update",
"name":"newRoles"
}
*/
class DocumentedParam extends DocumentedItem {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
this.directData.type = new DocumentedVarType(this, data.type);
}
serialize() {
super.serialize();
const { name, description, type } = this.directData;
return {
name,
description,
type: type.serialize(),
};
}
}
module.exports = DocumentedParam;

View file

@ -0,0 +1,44 @@
const DocumentedItem = require('./DocumentedItem');
const DocumentedItemMeta = require('./DocumentedItemMeta');
const DocumentedVarType = require('./DocumentedVarType');
/*
{ id: 'StringResolvable',
longname: 'StringResolvable',
name: 'StringResolvable',
scope: 'global',
kind: 'typedef',
description: 'Data that can be resolved to give a String...',
type: { names: [ 'String', 'Array', 'Object' ] },
meta:
{ lineno: 142,
filename: 'ClientDataResolver.js',
path: 'src/client' },
order: 37 }
*/
class DocumentedTypeDef extends DocumentedItem {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
this.directData.meta = new DocumentedItemMeta(this, data.meta);
this.directData.type = new DocumentedVarType(this, data.type);
}
serialize() {
super.serialize();
const { id, name, description, type, access, meta } = this.directData;
return {
id,
name,
description,
type: type.serialize(),
access,
meta: meta.serialize(),
};
}
}
module.exports = DocumentedTypeDef;

View file

@ -0,0 +1,47 @@
const DocumentedItem = require('./DocumentedItem');
/*
{
"names":[
"String"
]
}
*/
const regex = /([\w]+)([^\w]+)/;
const regexG = /([\w]+)([^\w]+)/g;
function splitVarName(str) {
const matches = str.match(regexG);
const output = [];
if (matches) {
for (const match of matches) {
const groups = match.match(regex);
output.push([groups[1], groups[2]]);
}
} else {
output.push([str.match(/(\w+)/g)[0], '']);
}
return output;
}
class DocumentedVarType extends DocumentedItem {
registerMetaInfo(data) {
super.registerMetaInfo(data);
this.directData = data;
}
serialize() {
super.serialize();
const names = [];
for (const name of this.directData.names) {
names.push(splitVarName(name));
}
return {
types: names,
};
}
}
module.exports = DocumentedVarType;

View file

@ -5,7 +5,7 @@
"main": "./src/index",
"scripts": {
"test": "eslint src/ && node test/random",
"docs": "node docs/gen/index.js"
"docs": "node docs/generator/generator.js"
},
"repository": {
"type": "git",