Doc components

This commit is contained in:
Tim Schaub
2018-05-28 11:35:55 -06:00
parent ee27cf1e01
commit d77ff64ec2
7 changed files with 368 additions and 129 deletions

38
site/package-lock.json generated
View File

@@ -11734,6 +11734,44 @@
}
}
},
"react-markdown": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-3.3.2.tgz",
"integrity": "sha512-3GzTB2JY+ciBcpok/t1acM49gAJTuzjSdwbG2+L6r31V6EsZqwx/F0Ht2sbuuKGyGmFCmORpxNrowT2VVbzv+w==",
"dev": true,
"requires": {
"prop-types": "^15.6.1",
"remark-parse": "^5.0.0",
"unified": "^6.1.5",
"unist-util-visit": "^1.3.0",
"xtend": "^4.0.1"
},
"dependencies": {
"remark-parse": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz",
"integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==",
"dev": true,
"requires": {
"collapse-white-space": "^1.0.2",
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-whitespace-character": "^1.0.0",
"is-word-character": "^1.0.0",
"markdown-escapes": "^1.0.0",
"parse-entities": "^1.1.0",
"repeat-string": "^1.5.4",
"state-toggle": "^1.0.0",
"trim": "0.0.1",
"trim-trailing-lines": "^1.0.0",
"unherit": "^1.0.4",
"unist-util-remove-position": "^1.0.0",
"vfile-location": "^2.0.0",
"xtend": "^4.0.1"
}
}
}
},
"react-proxy": {
"version": "3.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz",

View File

@@ -21,6 +21,7 @@
"prop-types": "15.6.1",
"react-emotion": "^9.1.3",
"react-helmet": "5.2.0",
"react-markdown": "^3.3.2",
"rollup": "^0.58.1",
"rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-node-resolve": "^3.3.0",

View File

@@ -0,0 +1,76 @@
import React, {Component, Fragment} from 'react';
import {string, array} from 'prop-types';
import Markdown from 'react-markdown';
import {slugify, getShortName, getShortModuleName} from '../../utils/doc';
class Class extends Component {
static propTypes = {
name: string.isRequired,
description: string,
params: array,
exported: string
};
renderArguments() {
if (!this.props.params) {
return null;
}
return (
<Fragment>
<h4>Arguments</h4>
<ul>{this.props.params.map(this.renderArgument)}</ul>
</Fragment>
);
}
renderArgument(arg) {
return (
<li key={arg.name}>
<code>{arg.name}</code>: {arg.description}
</li>
);
}
render() {
const name = this.props.name;
const shortName = getShortName(name);
const moduleName = getShortModuleName(name);
const slug = slugify(name);
const exported = this.props.exported;
let importSyntax;
if (exported) {
if (exported === 'default') {
importSyntax = `import ${shortName} from '${moduleName}';\n\n`;
} else if (exported !== shortName) {
importSyntax = `import {${exported} as ${shortName}} from '${moduleName}';\n\n`;
} else {
importSyntax = `import {${exported}} from '${moduleName}';\n\n`;
}
}
const params = this.props.params || [];
const usage = `new ${shortName}(${params
.map(param => param.name)
.join(', ')});`;
const description = this.props.description || '';
return (
<Fragment>
<a name={slug} href={`#${slug}`} />
<pre>
<code>
{importSyntax}
{usage}
</code>
</pre>
<Markdown source={description} />
{this.renderArguments()}
</Fragment>
);
}
}
export default Class;

View File

@@ -0,0 +1,76 @@
import React, {Component, Fragment} from 'react';
import {string, array} from 'prop-types';
import Markdown from 'react-markdown';
import {slugify, getShortName, getShortModuleName} from '../../utils/doc';
class Func extends Component {
static propTypes = {
name: string.isRequired,
description: string,
params: array,
exported: string
};
renderArguments() {
if (!this.props.params) {
return null;
}
return (
<Fragment>
<h4>Arguments</h4>
<ul>{this.props.params.map(this.renderArgument)}</ul>
</Fragment>
);
}
renderArgument(arg) {
return (
<li key={arg.name}>
<code>{arg.name}</code>: {arg.description}
</li>
);
}
render() {
const name = this.props.name;
const shortName = getShortName(name);
const moduleName = getShortModuleName(name);
const slug = slugify(name);
const exported = this.props.exported;
let importSyntax;
if (exported) {
if (exported === 'default') {
importSyntax = `import ${shortName} from '${moduleName}';\n\n`;
} else if (exported !== shortName) {
importSyntax = `import {${exported} as ${shortName}} from '${moduleName}';\n\n`;
} else {
importSyntax = `import {${exported}} from '${moduleName}';\n\n`;
}
}
const params = this.props.params || [];
const usage = `${shortName}(${params
.map(param => param.name)
.join(', ')});`;
const description = this.props.description || '';
return (
<Fragment>
<a name={slug} href={`#${slug}`} />
<pre>
<code>
{importSyntax}
{usage}
</code>
</pre>
<Markdown source={description} />
{this.renderArguments()}
</Fragment>
);
}
}
export default Func;

View File

@@ -0,0 +1,57 @@
import React, {Component, Fragment} from 'react';
import {string, array} from 'prop-types';
import {slugify, getShortModuleName} from '../../utils/doc';
import Func from './Func';
import Class from './Class';
class Module extends Component {
static propTypes = {
name: string.isRequired,
classes: array.isRequired,
functions: array.isRequired
};
renderClasses() {
if (this.props.classes.length === 0) {
return null;
}
return (
<Fragment>
<h3>Classes</h3>
{this.props.classes.map(cls => <Class key={cls.name} {...cls} />)}
</Fragment>
);
}
renderFuncs() {
if (this.props.functions.length === 0) {
return null;
}
return (
<Fragment>
<h3>Functions</h3>
{this.props.functions.map(func => <Func key={func.name} {...func} />)}
</Fragment>
);
}
render() {
const name = this.props.name;
const slug = slugify(name);
return (
<section>
<a name={slug} href={`#${slug}`}>
<h1>
<code>{getShortModuleName(name)}</code>
</h1>
</a>
{this.renderClasses()}
{this.renderFuncs()}
</section>
);
}
}
export default Module;

View File

@@ -1,138 +1,18 @@
import React, {Component} from 'react';
import React, {Component, Fragment} from 'react';
import Module from '../components/doc/Module';
import {getModules} from '../utils/doc';
import info from '../../../build/info.json';
const modules = [];
const moduleLookup = {};
info.modules.forEach(mod => {
moduleLookup[mod.path] = mod;
modules.push(mod);
});
info.symbols.forEach(symbol => {
const mod = moduleLookup[symbol.path];
if (!mod) {
throw new Error(`No module for symbol ${symbol.name}`);
}
if (
(symbol.memberof && symbol.memberof.indexOf('~') !== -1) ||
symbol.kind === 'class'
) {
const name = symbol.kind === 'class' ? symbol.name : symbol.memberof;
if (!mod.classes) {
mod.classes = {};
}
if (!mod.classes[name]) {
mod.classes[name] = {};
}
mod.classes[name][symbol.name] = symbol;
}
if (!mod.symbols) {
mod.symbols = [];
}
mod.symbols.push(symbol);
});
function getModuleName(longname) {
return longname.slice(7);
}
function getName(longname) {
return longname.split(/[~\.]/).pop();
}
function isMember(symbol) {
return symbol.name.indexOf('#') !== -1;
}
function slugify(name) {
return name.replace(/[#~\.]/g, '-');
}
const modules = getModules(info);
class Docs extends Component {
renderModule = mod => {
const slug = slugify(mod.name);
return (
<section key={mod.name}>
<a name={slug} href={`#${slug}`}>
<h1>{getModuleName(mod.name)}</h1>
<h2>Classes</h2>
{mod.classes &&
Object.keys(mod.classes).map(cls => this.renderClass(cls, mod))}
<h2>Functions</h2>
{mod.symbols &&
mod.symbols
.filter(sym => sym.kind === 'function' && !isMember(sym))
.map(fn => this.renderFunction(fn, mod))}
<h2>Constants</h2>
{mod.symbols &&
mod.symbols
.filter(sym => sym.kind === 'constant' && !isMember(sym))
.map(constant => this.renderConstant(constant, mod))}
</a>
</section>
);
};
renderImport(longname, mod) {
return (
<code>
import {getName(longname)} from &apos;{getModuleName(mod.name)}&apos;;
</code>
);
}
renderParams(params) {
//TODO Render types in a more structured way (like in default template?)
//TODO Use markdown for description
return (
<ul>
{params.map(param => (
<li key={param.name}>
{param.name}: {param.types.join('|')}
<br />
{param.description}
</li>
))}
</ul>
);
}
renderConstructor(cls, mod) {
if (cls in mod.classes && cls in mod.classes[cls]) {
const params = mod.classes[cls][cls].params;
return (
<div>
<p>{this.renderImport(cls, mod)}</p>
<h4>
new {getName(cls)}({params.map(p => p.name).join(', ')})
</h4>
<h5>Parameters</h5>
{this.renderParams(params)}
</div>
);
}
}
renderClass(cls, mod) {
return (
<div key={cls}>
<h3>{getName(cls)}</h3>
{this.renderConstructor(cls, mod)}
</div>
);
}
renderFunction(fn, mod) {
return <p key={fn.name}>{this.renderImport(fn.name, mod)}</p>;
}
renderConstant(constant, mod) {
return <p key={constant.name}>{this.renderImport(constant.name, mod)}</p>;
}
render() {
return <div>{modules.map(this.renderModule)}</div>;
return (
<Fragment>
{modules.map(mod => <Module key={mod.name} {...mod} />)}
</Fragment>
);
}
}

111
site/src/utils/doc.js Normal file
View File

@@ -0,0 +1,111 @@
function getLongModuleName(name) {
return name.split(/[~\.]/).shift();
}
export function getShortModuleName(longname) {
return getLongModuleName(longname).slice(7);
}
export function getShortName(longname) {
return longname.split(/[~\.]/).pop();
}
export function slugify(name) {
return name.replace(/[#~\.]/g, '-');
}
function getExported(name, exports) {
const local = getShortName(name);
for (const exported in exports) {
if (exports[exported] === local) {
return exported;
}
}
}
export function getModules(info) {
const moduleLookup = {};
info.modules.forEach(mod => {
moduleLookup[mod.name] = {...mod, functions: [], classes: []};
});
// extract classes
const classLookup = {};
info.symbols.forEach(symbol => {
const name = symbol.name;
const parent = symbol.memberof;
if (symbol.kind === 'class') {
const mod = moduleLookup[parent];
if (!mod) {
throw new Error(
`No module found for class ${name} with parent ${parent}`
);
}
const cls = {
...symbol,
methods: [],
properties: [],
exported: getExported(name, mod.exports)
};
mod.classes.push(cls);
classLookup[name] = cls;
}
});
info.symbols.forEach(symbol => {
const name = symbol.name;
const parent = symbol.memberof;
if (symbol.kind === 'member') {
if (parent in classLookup) {
classLookup[parent].properties.push(symbol);
return;
}
// instance property where constructor is not marked @api
const moduleId = getLongModuleName(name);
const mod = moduleLookup[moduleId];
if (!mod) {
throw new Error(`Unexpected member: ${name}`);
}
const cls = {name: parent, methods: [], properties: [symbol]};
mod.classes.push(cls);
classLookup[parent] = cls;
return;
}
if (symbol.kind === 'function') {
if (parent in moduleLookup) {
moduleLookup[parent].functions.push({
...symbol,
exported: getExported(name, moduleLookup[parent].exports)
});
return;
}
if (parent in classLookup) {
classLookup[parent].methods.push(symbol);
return;
}
// method where constructor is not marked @api
const moduleId = getLongModuleName(name);
const mod = moduleLookup[moduleId];
if (!mod) {
throw new Error(`Unexpected function: ${name}`);
}
const cls = {name: parent, methods: [symbol], properties: []};
mod.classes.push(cls);
classLookup[parent] = cls;
return;
}
});
return Object.keys(moduleLookup)
.sort()
.map(id => moduleLookup[id]);
}