All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.ceylondoc.CeylonDoc Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * 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