com.redhat.ceylon.ceylondoc.CeylonDoc Maven / Gradle / Ivy
/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.ceylondoc;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.model.loader.AbstractModelLoader;
import com.redhat.ceylon.model.typechecker.model.Annotated;
import com.redhat.ceylon.model.typechecker.model.Annotation;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Constructor;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Interface;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.ModuleImport;
import com.redhat.ceylon.model.typechecker.model.NothingType;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.Referenceable;
import com.redhat.ceylon.model.typechecker.model.TypeAlias;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
public abstract class CeylonDoc extends Markup {
protected final CeylonDocTool tool;
protected final Module module;
protected final Map keyboardShortcuts = new HashMap();
public CeylonDoc(Module module, CeylonDocTool tool, Writer writer) {
super(writer);
this.module = module;
this.tool = tool;
}
protected final LinkRenderer linkRenderer() {
return new LinkRenderer(tool, writer, getFromObject());
}
protected final void writeHeader(String title, String... additionalCss) throws IOException {
write("");
open("html");
open("head");
tag("meta charset='UTF-8'");
around("title", title);
tag("link href='" + linkRenderer().getResourceUrl("favicon.ico") + "' rel='shortcut icon'");
tag("link href='" + linkRenderer().getResourceUrl("ceylon.css") + "' rel='stylesheet' type='text/css'");
tag("link href='" + linkRenderer().getResourceUrl("bootstrap.min.css") + "' rel='stylesheet' type='text/css'");
tag("link href='" + linkRenderer().getResourceUrl("ceylondoc.css") + "' rel='stylesheet' type='text/css'");
tag("link href='//fonts.googleapis.com/css?family=Source+Sans+Pro|Inconsolata|Inconsolata:700|PT+Sans|PT+Sans:700' rel='stylesheet' type='text/css'");
for (String css : additionalCss) {
if (!css.endsWith(".css")) {
throw new RuntimeException(CeylondMessages.msg("error.unexpectedAdditionalResource", css));
}
tag("link href='" + linkRenderer().getResourceUrl(css) + "' rel='stylesheet' type='text/css'");
}
close("head");
open("body");
if (!Util.isEmpty(tool.getHeader())) {
around("header", tool.getHeader());
}
}
protected final void writeFooter(String... additionalJs) throws IOException {
open("script type='text/javascript'");
write("var resourceBaseUrl = '" + tool.getResourceUrl(getFromObject(), "") + "'");
close("script");
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl("jquery-1.8.2.min.js") + "'");
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl("bootstrap.min.js") + "'");
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl("rainbow.min.js") + "'");
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl("ceylon.js") + "'");
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl("index.js") + "'");
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl("ceylondoc.js") + "'");
for (String js : additionalJs) {
if (!js.endsWith(".js")) {
throw new RuntimeException(CeylondMessages.msg("error.unexpectedAdditionalResource", js));
}
around("script type='text/javascript' src='" + linkRenderer().getResourceUrl(js) + "'");
}
writeKeyboardShortcuts();
if (!Util.isEmpty(tool.getFooter())) {
around("footer", tool.getFooter());
}
close("body");
close("html");
}
protected final void writeNavBar() throws IOException {
open("div class='navbar navbar-inverse navbar-static-top'");
open("div class='navbar-inner'");
open("a class='module-header' href='" + linkRenderer().to(module).getUrl() + "'");
around("i class='module-logo'");
around("span class='module-label'", "module");
around("span class='module-name'", module.getNameAsString());
around("span class='module-version'", module.getVersion() +
(module.isNative() ? " (" + module.getNativeBackends().names() + ")" : ""));
close("a");
open("ul class='nav pull-right'");
write("");
writeNavBarExpandAllCollapseAll();
writeNavBarInfoMenu();
close("ul"); // nav
open("ul class='nav pull-right'");
write("");
writeNavBarIndexMenu();
writeNavBarSearchMenu();
writeNavBarFilterMenu();
close("ul"); // nav
close("div"); // navbar-inner
close("div"); // navbar
}
protected void writeNavBarExpandAllCollapseAll() throws IOException {
write(" ");
write(" ");
}
protected void writeNavBarInfoMenu() throws IOException {
open("li id='infoDropdown' class='dropdown'");
write("");
open("ul id='info-dropdown-panel' class='dropdown-menu'");
around("h4", "Keyboard Shortcuts");
write("");
open("div class='row-fluid'");
open("div id='info-common-shortcuts' class='span6'");
writeKeyboardShortcutInfo("f", "Open filter by tags");
writeKeyboardShortcutInfo("s", "Open search page");
writeKeyboardShortcutInfo("?", "Open this information panel");
close("div"); // info-common-shortcuts
open("div id='info-expand-collapse-shortcuts' class='span6'");
writeKeyboardShortcutInfo("+", "Expand all");
writeKeyboardShortcutInfo("-", "Collapse all");
close("div"); // info-expand-collapse-shortcuts
close("div"); // row-fluid
write("");
open("div class='row-fluid'");
open("div id='info-doc-shortcuts' class='span6'");
around("h5","Documentation:");
writeKeyboardShortcutInfo("o", "Jump to module documentation");
writeKeyboardShortcutInfo("p", "Jump to package documentation");
writeKeyboardShortcutInfo("l", "Jump to aliases");
writeKeyboardShortcutInfo("n", "Jump to annotations");
writeKeyboardShortcutInfo("z", "Jump to initializer");
writeKeyboardShortcutInfo("t", "Jump to constructors");
if( getFromObject() instanceof Module || getFromObject() instanceof Package ) {
writeKeyboardShortcutInfo("v", "Jump to values");
writeKeyboardShortcutInfo("f", "Jump to functions");
} else {
writeKeyboardShortcutInfo("a", "Jump to attributes");
writeKeyboardShortcutInfo("m", "Jump to methods");
}
writeKeyboardShortcutInfo("i", "Jump to interfaces");
writeKeyboardShortcutInfo("c", "Jump to classes");
writeKeyboardShortcutInfo("e", "Jump to exceptions");
close("div"); // info-doc-shortcuts
open("div id='info-search-shortcuts' class='span6'");
around("h5", "Search page:");
writeKeyboardShortcutInfo("enter", "Jump to selected declaration");
writeKeyboardShortcutInfo("esc", "Clear search query/Go to overview");
writeKeyboardShortcutInfo("up", "Move selection up");
writeKeyboardShortcutInfo("down", "Move selection down");
close("div"); // info-search-shortcuts
close("div"); // row-fluid
close("ul"); // dropdown-menu
close("li"); // dropdown
}
private void writeKeyboardShortcutInfo(String key, String info) throws IOException {
open("div id='"+key+"'");
around("span class='key badge'", key);
around("span class='info muted'", info);
close("div");
}
protected void writeNavBarIndexMenu() throws IOException {
write("Index ");
}
protected void writeNavBarSearchMenu() throws IOException {
write("Search ");
}
protected void writeNavBarFilterMenu() throws IOException {
open("li id='filterDropdown' class='dropdown'");
write("Filter ");
open("ul id='filterDropdownPanel' class='dropdown-menu'");
around("h4 id='filterDropdownPanelInfo'", "Filter declarations by tags");
write("");
write("");
write("");
open("div id='filterActions'");
write("All");
write("None");
write("Show more");
close("div"); // filterActions
close("ul"); // filterDropdownPanel
close("li"); // filterDropdown
}
protected final void writeSubNavBarLink(String href, String text, char key, String tooltip) throws IOException {
open("a href='" + href + "'");
int index = text.indexOf(key);
if( index == -1 ) {
write("", text, "");
} else {
String before = text.substring(0, index);
String after = text.substring(index+1);
write("");
write(before, "", String.valueOf(key), "", after, "");
}
close("a");
}
protected void writeKeyboardShortcuts() throws IOException{
registerKeyboardShortcut('s', linkRenderer().getResourceUrl("../search.html"));
registerKeyboardShortcut('o', linkRenderer().getResourceUrl("../index.html"));
registerAdditionalKeyboardShortcuts();
if( !keyboardShortcuts.isEmpty() ) {
open("script type='text/javascript'");
write("jQuery('html').keypress(function(evt){\n");
write(" evt = evt || window.event;\n");
write(" var keyCode = evt.keyCode || evt.which;\n");
write(" if( !evt.ctrlKey && !evt.altKey ) {\n");
write(" if (keyCode == " + (int)'?' + ") {\n");
write(" $('#infoDropdown > .dropdown-toggle').click();\n");
write(" }\n");
for(Map.Entry keyboardShortcut : keyboardShortcuts.entrySet()) {
write(" if(keyCode == "+(int)keyboardShortcut.getKey().charValue()+"){\n");
write(" document.location = '"+keyboardShortcut.getValue()+"';\n");
write(" }\n");
}
write(" }\n");
write("});\n");
write("enableInfoKeybordShortcut('\\\\?');\n");
for (Map.Entry keyboardShortcut : keyboardShortcuts.entrySet()) {
write("enableInfoKeybordShortcut('"+ keyboardShortcut.getKey() + "');\n");
}
close("script");
}
}
protected void registerAdditionalKeyboardShortcuts() throws IOException {
// for subclasses
}
protected final void registerKeyboardShortcut(char c, String url) throws IOException {
keyboardShortcuts.put(c, url);
}
protected final void writeLinkSourceCode(Object obj) throws IOException {
String srcUrl = linkRenderer().getSrcUrl(obj);
if (tool.isIncludeSourceCode() && srcUrl != null) {
open("a class='link-source-code' href='" + srcUrl + "'");
write("");
write("Source Code");
close("a");
}
}
protected final void writeBy(Object obj) throws IOException {
List annotations;
if( obj instanceof Declaration ) {
annotations = ((Declaration) obj).getAnnotations();
} else if ( obj instanceof Module ) {
annotations = ((Module) obj).getAnnotations();
} else if( obj instanceof Package ) {
annotations = ((Package) obj).getAnnotations();
} else {
throw new IllegalArgumentException();
}
List authors = new ArrayList<>();
for (Annotation annotation : annotations) {
if (annotation.getName().equals("by")) {
for (String author : annotation.getPositionalArguments()) {
authors.add(author);
}
}
}
if (!authors.isEmpty()) {
open("div class='by section'");
around("span class='title'", "By: ");
around("span class='value'", Util.join(", ", authors));
close("div");
}
}
protected final void writeSee(T decl) throws IOException {
Annotation see = Util.getAnnotation(decl.getUnit(), decl.getAnnotations(), "see");
if(see == null)
return;
open("div class='see section'");
around("span class='title'", "See also ");
open("span class='value'");
boolean first = true;
for (String target : see.getPositionalArguments()) {
if (!first) {
write(", ");
} else {
first = false;
}
//TODO: add 'identifier' or 'type-identitier' CSS class
linkRenderer().to(target).withinText(true).useScope(decl)
.printAbbreviated(false).printTypeParameters(false).write();
}
close("span");
close("div");
}
protected final void writeSince(T decl) throws IOException {
Annotation see = Util.getAnnotation(decl.getUnit(), decl.getAnnotations(), "since");
if(see == null)
return;
open("div class='since section'");
around("span class='title'", "Since ");
open("span class='value'");
boolean first = true;
for (String version : see.getPositionalArguments()) {
if (!first) {
write(", ");
} else {
first = false;
}
write(version);
}
close("span");
close("div");
}
protected final void writeIcon(Object obj) throws IOException {
List icons = getIcons(obj);
int i = 0;
for (String icon : icons) {
open("i class='" + icon + "'");
i++;
}
while (i-- > 0) {
close("i");
}
}
protected final List getIcons(Object obj) {
List icons = new ArrayList();
if( obj instanceof Declaration ) {
Declaration decl = (Declaration) obj;
Annotation deprecated = Util.findAnnotation(decl, "deprecated");
if (deprecated != null) {
icons.add("icon-decoration-deprecated");
}
if( decl instanceof ClassOrInterface || decl instanceof Constructor ) {
if (decl instanceof Interface) {
icons.add("icon-interface");
if (Util.isEnumerated((ClassOrInterface) decl)) {
icons.add("icon-decoration-enumerated");
}
}
if (decl instanceof Class) {
Class klass = (Class) decl;
if (klass.isAnonymous()) {
icons.add("icon-object");
} else {
icons.add("icon-class");
}
if (klass.isAbstract()) {
icons.add("icon-decoration-abstract");
}
if (klass.isFinal() && !klass.isAnonymous() && !klass.isAnnotation()) {
icons.add("icon-decoration-final");
}
if (Util.isEnumerated(klass)) {
icons.add("icon-decoration-enumerated");
}
}
if (decl instanceof Constructor) {
icons.add("icon-class");
}
if (!decl.isShared() ) {
icons.add("icon-decoration-local");
}
}
if (decl instanceof TypedDeclaration) {
if( decl.isShared() ) {
icons.add("icon-shared-member");
}
else {
icons.add("icon-local-member");
}
if( decl.isFormal() ) {
icons.add("icon-decoration-formal");
}
if (decl.isActual()) {
Declaration refinedDeclaration = decl.getRefinedDeclaration();
if (refinedDeclaration != null) {
if (refinedDeclaration.isFormal()) {
icons.add("icon-decoration-impl");
}
if (refinedDeclaration.isDefault()) {
icons.add("icon-decoration-over");
}
}
}
if( ((TypedDeclaration) decl).isVariable() ) {
icons.add("icon-decoration-variable");
}
}
if (decl instanceof TypeAlias || decl instanceof NothingType) {
icons.add("icon-type-alias");
}
if (decl.isAnnotation()) {
icons.add("icon-decoration-annotation");
}
}
if (obj instanceof Package) {
Package pkg = (Package) obj;
icons.add("icon-package");
if (!pkg.isShared()) {
icons.add("icon-decoration-local");
}
}
if (obj instanceof ModuleImport) {
ModuleImport moduleImport = (ModuleImport) obj;
icons.add("icon-module");
if (moduleImport.isExport()) {
icons.add("icon-module-exported-decoration");
}
if (moduleImport.isOptional()) {
icons.add("icon-module-optional-decoration");
}
}
if (obj instanceof Module) {
icons.add("icon-module");
}
return icons;
}
protected final void writePackageNavigation(Package pkg) throws IOException {
open("span class='package-identifier'");
if (!module.isDefaultModule()) {
List moduleNames = module.getName();
List pkgNames = pkg.getName();
List subpkgNames = pkgNames.subList(moduleNames.size(), pkgNames.size());
linkRenderer().to(module.getRootPackage()).write();
if (!subpkgNames.isEmpty()) {
StringBuilder subpkgNameBuilder = new StringBuilder(module.getNameAsString());
for (String subpkgName : subpkgNames) {
subpkgNameBuilder.append(".").append(subpkgName);
Package subpkg = module.getDirectPackage(subpkgNameBuilder.toString());
write(".");
if (subpkg != null) {
linkRenderer().to(subpkg).useCustomText(subpkgName).write();
} else {
write(subpkgName);
}
}
}
} else {
linkRenderer().to(pkg).write();
}
close("span");
}
protected final void writePackagesTable(String title, List packages) throws IOException {
if (!packages.isEmpty()) {
openTable("section-packages", title, 2, true);
for (Package pkg : packages) {
writePackagesTableRow(pkg);
}
closeTable();
}
}
protected final void writePackagesTableRow(Package pkg) throws IOException {
open("tr");
open("td");
writeIcon(pkg);
if (pkg.getNameAsString().isEmpty()) {
around("a class='link' href='index.html'", "default package");
} else {
around("a class='link' href='" + tool.getObjectUrl(getFromObject(), pkg) + "'", pkg.getNameAsString());
}
close("td");
open("td");
writeTagged(pkg);
write(Util.getDocFirstLine(pkg, linkRenderer()));
close("td");
close("tr");
}
protected final void writeAnnotations(Referenceable referenceable) throws IOException {
Tree.AnnotationList annotationList = null;
Node node = tool.getNode(referenceable);
if (node instanceof Tree.Declaration) {
annotationList = ((Tree.Declaration) node).getAnnotationList();
} else if (node instanceof Tree.ImportModule) {
annotationList = ((Tree.ImportModule) node).getAnnotationList();
} else if (node instanceof Tree.ModuleDescriptor) {
annotationList = ((Tree.ModuleDescriptor) node).getAnnotationList();
} else if (node instanceof Tree.PackageDescriptor) {
annotationList = ((Tree.PackageDescriptor) node).getAnnotationList();
}
if (annotationList != null) {
annotationList.visit(new WriteAnnotationsVisitor(referenceable));
}
}
private class WriteAnnotationsVisitor extends Visitor {
private final Referenceable referenceable;
private Tree.Annotation annotation;
private Tree.InvocationExpression invocationExpression;
private Tree.PositionalArgument positionalArgument;
public WriteAnnotationsVisitor(Referenceable referenceable) {
this.referenceable = referenceable;
}
@Override
public void visit(Tree.AnnotationList that) {
try {
boolean containsAnnotations = false;
for (Tree.Annotation annotation : that.getAnnotations()) {
if (!isCeylonLanguageAnnotation(annotation)) {
containsAnnotations = true;
break;
}
}
if( containsAnnotations ) {
open("div class='annotations section'");
around("span class='title'", "Annotations: ");
open("ul");
super.visit(that);
close("ul");
close("div");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.AnonymousAnnotation that) {
// noop
}
@Override
public void visit(Tree.Annotation that) {
try {
if (isCeylonLanguageAnnotation(that)) {
return;
}
open("li");
Tree.Annotation old = annotation;
annotation = that;
super.visit(that);
annotation = old;
close("li");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.MemberOrTypeExpression that) {
try {
linkRenderer().to(that.getDeclaration()).useScope(referenceable).printParenthesisAfterMethodName(false).write();
super.visit(that);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.InvocationExpression that) {
Tree.InvocationExpression old = invocationExpression;
invocationExpression = that;
super.visit(that);
invocationExpression = old;
}
@Override
public void visit(Tree.PositionalArgumentList that) {
try {
if (!that.getPositionalArguments().isEmpty() || annotation != invocationExpression) {
write("(");
}
positionalArgument = null;
super.visit(that);
positionalArgument = null;
if (!that.getPositionalArguments().isEmpty() || annotation != invocationExpression) {
write(")");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.PositionalArgument that) {
try {
if (positionalArgument != null) {
write(", ");
}
super.visit(that);
positionalArgument = that;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.NamedArgumentList that) {
try {
write("{");
super.visit(that);
write("}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.NamedArgument that) {
try {
write(that.getParameter().getName());
write("=");
super.visit(that);
write(";");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.SequenceEnumeration that) {
try {
Tree.PositionalArgument old = positionalArgument;
positionalArgument = null;
write("{");
super.visit(that);
write("}");
positionalArgument = old;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.Tuple that) {
try {
Tree.PositionalArgument old = positionalArgument;
positionalArgument = null;
write("[");
super.visit(that);
write("]");
positionalArgument = old;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.Literal that) {
try {
open("span class='literal'");
if (that instanceof Tree.StringLiteral) {
write(""");
}
write(that.getText());
if (that instanceof Tree.StringLiteral) {
write(""");
}
close("span");
super.visit(that);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.MemberLiteral that) {
try {
if (that instanceof Tree.ValueLiteral) {
write("`");
around("span class='keyword'", "value ");
} else if (that instanceof Tree.FunctionLiteral) {
write("`");
around("span class='keyword'", "function ");
}
linkRenderer().to(that.getDeclaration()).useScope(referenceable).write();
write("`");
super.visit(that);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.TypeLiteral that) {
try {
if (that instanceof Tree.AliasLiteral) {
write("`");
around("span class='keyword'", "alias ");
} else if (that instanceof Tree.ClassLiteral) {
write("`");
around("span class='keyword'", "class ");
} else if (that instanceof Tree.InterfaceLiteral) {
write("`");
around("span class='keyword'", "interface ");
} else if (that instanceof Tree.TypeParameterLiteral) {
write("`");
around("span class='keyword'", "given ");
}
linkRenderer().to(that.getDeclaration()).useScope(referenceable).write();
write("`");
super.visit(that);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.ModuleLiteral that) {
try {
write("`");
around("span class='keyword'", "module ");
linkRenderer().to(that.getImportPath().getModel()).useScope(referenceable).write();
write("`");
super.visit(that);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(Tree.PackageLiteral that) {
try {
write("`");
around("span class='keyword'", "package");
write(" ");
linkRenderer().to(that.getImportPath().getModel()).useScope(referenceable).write();
write("`");
super.visit(that);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private boolean isCeylonLanguageAnnotation(Tree.Annotation annotation) {
if (annotation.getPrimary() instanceof Tree.MemberOrTypeExpression) {
Declaration declaration = ((Tree.MemberOrTypeExpression) annotation.getPrimary()).getDeclaration();
if (declaration.getQualifiedNameString().startsWith(AbstractModelLoader.CEYLON_LANGUAGE)) {
return true;
}
}
return false;
}
}
protected final void writeTagged(T decl) throws IOException {
List tags = Util.getTags(decl);
if (!tags.isEmpty()) {
open("div class='tags section'");
Iterator tagIterator = tags.iterator();
while (tagIterator.hasNext()) {
String tag = tagIterator.next();
write("" + tag + "");
}
close("div");
}
}
protected abstract Object getFromObject();
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy