/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from "fs";
import * as path from "path";
import { Transform } from "stream";
import { File, FileTransform } from "async-build";
import * as AST from "./typescript/ast";
import { Compiler } from "./typescript/compiler";
import { walk } from "./typescript/walker";
function flatten(arr: T[][]): T[] {
var result: T[] = [];
for (const a of arr) {
result = result.concat(a);
}
return result;
}
var sorter = (() => {
function visibilitySorter(value1: { isPrivate?: boolean; isProtected?: boolean; }, value2: { isPrivate?: boolean; isProtected?: boolean; }) {
if (value1.isPrivate === value2.isPrivate && value1.isProtected === value2.isProtected) {
return 0;
}
if (value1.isPrivate) {
return 1;
}
if (value2.isPrivate) {
return -1;
}
if (value1.isProtected) {
return 1;
}
if (value2.isProtected) {
return -1;
}
return 0;
}
var types = [AST.Property, AST.Function, AST.Interface, AST.Class, AST.Enum];
function typeSorter(value1: AST.ModuleMember | AST.NamespaceMember, value2: AST.ModuleMember | AST.NamespaceMember) {
var type1Index = -1;
var type2Index = -1;
types.every((type, index) => {
if (value1 instanceof type) {
type1Index = index;
}
if (value2 instanceof type) {
type2Index = index;
}
return (type1Index === -1) || (type2Index === -1);
});
return type1Index - type2Index;
}
function nameSorter(value1: { name: string }, value2: { name: string }) {
return value1.name.localeCompare(value2.name);
}
var sorters: ((value1: AST.ModuleMember, value2: AST.ModuleMember) => number)[] = [visibilitySorter, typeSorter, nameSorter];
return (value1: AST.ModuleMember, value2: AST.ModuleMember) => {
for (var i = 0; i < sorters.length; i++) {
var result = sorters[i](value1, value2);
if (result !== 0) {
return result;
}
}
return 0;
};
})();
function indenter(indent: number) {
return (line: string) => ((line === "") ? line : (Array(indent + 1).join("\t") + line));
}
function sanitize(str: string) {
return str.replace(/&/g, "&").replace(//g, ">");
}
function toVariableName(item: { name: string }) {
// TODO: Handle non-letters (are both their toLowerCase() and toUpperCase())
var name = item.name;
var result = "";
for (var i = 0; i < name.length; i++) {
if (name[i] === name[i].toLowerCase()) {
// This is lower case. Write it as lower case.
result += name[i];
}
else {
// This is upper case.
if (i === 0) {
// This is the first character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i - 1] === name[i - 1].toUpperCase()) {
// The previous character was upper case.
if (i === name.length - 1) {
// This is the last character. Write it as lower case.
result += name[i].toLowerCase();
}
else if (name[i + 1] === name[i + 1].toLowerCase()) {
// The next character is lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
else {
// The next character is upper case. Write this one as lower case.
result += name[i].toLowerCase();
}
}
else {
// Previous character was lower case so this is the start of a new word. Write this one as upper case.
result += name[i];
}
}
}
return result;
}
function toUsageName(item: AST.Class | AST.Interface | AST.Function | AST.Property | AST.Enum): string {
if (item.parent instanceof AST.Module) {
return item.name;
}
if (item instanceof AST.Class || item instanceof AST.Interface || item instanceof AST.Enum) {
if (item.isPrivate) {
return item.name;
}
return item.fullName;
}
if (item.parent instanceof AST.Namespace) {
if ((item).isPrivate) {
return item.name;
}
return item.fullName;
}
if ((item).isStatic) {
return toUsageName(item.parent) + '.' + item.name;
}
return toVariableName(item.parent) + '.' + item.name;
}
function toId(item: { fullName?: string; name: string; }): string {
return sanitize((item.fullName === undefined) ? item.name : item.fullName);
}
function toLink(item: AST.ModuleMember | AST.EnumMember | AST.TypeReference): string {
var result = `${ sanitize(item.name) }`;
var itemWithGenerics = item;
if (itemWithGenerics.generics !== undefined && itemWithGenerics.generics.length > 0) {
var generics = <(string | AST.TypeReference | AST.IntrinsicTypeReference)[]>itemWithGenerics.generics;
result += sanitize(`.<${ generics.map(generic =>
(generic instanceof AST.TypeReference || generic instanceof AST.IntrinsicTypeReference) ? generic.name : generic
).join(', ') }>`);
}
result += '';
return result;
}
function writeDescription(text: string): string {
var result = sanitize(text).replace(/\{@link ([^} ]+)\}/g, (substring, linkTarget) => `${ linkTarget }`);
var inCodeBlock = false;
result = result.split("\n").map(line => {
if (line.substr(0, " ".length) === " ") {
line = line.substr(" ".length);
if (!inCodeBlock) {
inCodeBlock = true;
line = `
${ line }`;
}
}
else if (line.length > 0 && inCodeBlock) {
inCodeBlock = false;
line = `
${ line }`;
}
else if (line.length === 0 && !inCodeBlock) {
line = "
";
}
return line;
}).join("\n");
if (inCodeBlock) {
result += '';
}
result = `
',
''
]);
}
export function build(outputFilePath: string, root: string, rootNamespaceName: string): FileTransform {
var compiler = new Compiler();
return new FileTransform(function (file: File): void {
var self: FileTransform = this;
// Compile
compiler.compile(file);
// Walk
var walkResult = walk(compiler, root, rootNamespaceName);
var namespaces = walkResult.namespaces;
var modules = walkResult.modules;
// Make HTML
var namespaceNames = Object.keys(namespaces)
.filter(namespaceName => namespaceName.substr(0, rootNamespaceName.length) === rootNamespaceName)
.sort((ns1, ns2) => ns1.localeCompare(ns2));
var moduleNames = Object.keys(modules).sort((ns1, ns2) => ns1.localeCompare(ns2));
moduleNames = moduleNames.filter(moduleName => Object.keys(modules[moduleName].members).length > 0);
self.push({
path: outputFilePath,
contents: Buffer.concat([new Buffer(
`
${ rootNamespaceName } API Documentation
`
)]).concat(flatten(namespaceNames.map(namespaceName => {
var namespace = namespaces[namespaceName];
var namespaceMembers: AST.NamespaceMember[] = [];
for (const memberName of Object.keys(namespace.members)) {
namespaceMembers.push(namespace.members[memberName]);
}
namespaceMembers.sort(sorter);
var properties = namespaceMembers.filter(member => member instanceof AST.Property);
var functions = namespaceMembers.filter(member => member instanceof AST.Function);
var interfaces = namespaceMembers.filter(member => member instanceof AST.Interface);
var classes = namespaceMembers.filter(member => member instanceof AST.Class);
var enums = namespaceMembers.filter(member => member instanceof AST.Enum);
var result = [new Buffer(
`
Namespace ${ sanitize(namespaceName) }
`
)];
if (properties.length > 0) {
result.push(new Buffer(
`
Properties
`
));
for (const property of properties) {
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (functions.length > 0) {
result.push(new Buffer(
`
Free functions
`
));
for (const func of functions) {
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (interfaces.length > 0) {
result.push(new Buffer(
`
Interfaces
`
));
for (const interfase of interfaces) {
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (classes.length > 0) {
result.push(new Buffer(
`
Classes
`
));
for (const clazz of classes) {
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (enums.length > 0) {
result.push(new Buffer(
`
Enums
`
));
for (const enumType of enums) {
result.push(new Buffer(enumToHtml(enumType).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
result.push(new Buffer(
`
`
));
return result;
}))).concat(flatten(moduleNames.map(moduleName => {
var module = modules[moduleName];
var moduleMembers: AST.ModuleMember[] = [];
for (const memberName of Object.keys(module.members)) {
var member = module.members[memberName];
if ((member).parent === module) {
moduleMembers.push(member);
}
}
if (moduleMembers.length === 0) {
return [];
}
moduleMembers.sort(sorter);
var properties = moduleMembers.filter(member => member instanceof AST.Property);
var functions = moduleMembers.filter(member => member instanceof AST.Function);
var interfaces = moduleMembers.filter(member => member instanceof AST.Interface);
var classes = moduleMembers.filter(member => member instanceof AST.Class);
var enums = moduleMembers.filter(member => member instanceof AST.Enum);
var result = [new Buffer(
`
Module ${ sanitize(moduleName) }
`
)];
if (properties.length > 0) {
result.push(new Buffer(
`
Properties
`
));
for (const property of properties) {
result.push(new Buffer(propertyToHtml(property).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (functions.length > 0) {
result.push(new Buffer(
`
Free functions
`
));
for (const func of functions) {
result.push(new Buffer(functionToHtml(func).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (interfaces.length > 0) {
result.push(new Buffer(
`
Interfaces
`
));
for (const interfase of interfaces) {
result.push(new Buffer(interfaceToHtml(interfase).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (classes.length > 0) {
result.push(new Buffer(
`
Classes
`
));
for (const clazz of classes) {
result.push(new Buffer(classToHtml(clazz).map(indenter(5)).join("\n")));
}
result.push(new Buffer(
`
`
));
}
if (enums.length > 0) {
result.push(new Buffer(
`