/* * Copyright 2012 James Moger * * 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. */ package org.moxie.ant; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.types.FileSet; import org.moxie.Build; import org.moxie.Dependency; import org.moxie.Doc; import org.moxie.DocElement; import org.moxie.DocMenu; import org.moxie.DocPage; import org.moxie.DocStructure; import org.moxie.Docs; import org.moxie.ExcludeText; import org.moxie.Load; import org.moxie.Logo; import org.moxie.NoMarkdown; import org.moxie.Prop; import org.moxie.References; import org.moxie.Regex; import org.moxie.Scope; import org.moxie.Substitute; import org.moxie.Toolkit; import org.moxie.Toolkit.Key; import org.moxie.WikiText; import org.moxie.WikiText.Syntax; import org.moxie.utils.FileUtils; import org.moxie.utils.LessUtils; import org.moxie.utils.StringUtils; public class MxDoc extends MxTask { Doc doc = new Doc(); String customLessFile; Logo logo; Logo favicon; List<org.moxie.Resource> resources = new ArrayList<org.moxie.Resource>(); public MxDoc() { super(); setTaskName("mx:doc"); } public DocStructure createStructure() { DocStructure struct = new DocStructure(); doc.structure = struct; return struct; } public References createReferences() { References references = new References(); doc.references = references; return references; } public Substitute createReplace() { Substitute sub = new Substitute(); doc.substitutions.add(sub); return sub; } public Load createLoad() { Load load = new Load(); doc.loads.add(load); return load; } public Prop createProperties() { Prop prop = new Prop(); doc.props.add(prop); return prop; } public NoMarkdown createNomarkdown() { NoMarkdown nomd = new NoMarkdown(); doc.nomarkdowns.add(nomd); return nomd; } private WikiText createWikiText() { WikiText nomd = new WikiText(); doc.nomarkdowns.add(nomd); return nomd; } public Regex createRegex() { Regex regex = new Regex(); doc.regexes.add(regex); return regex; } public org.moxie.Resource createResource() { org.moxie.Resource rsc = new org.moxie.Resource(); resources.add(rsc); return rsc; } public Logo createLogo() { logo = new Logo(); return logo; } public Logo createFavicon() { favicon = new Logo(); return favicon; } public DocPage createPage() { DocPage page = new DocPage(); doc.freeformPages.add(page); return page; } public void setName(String name) { doc.name = name; } public void setSourceDir(File folder) { doc.sourceDirectory = folder; } public void setTemplateDir(File folder) { doc.templateDirectory = folder; } public void setTodir(File dir) { doc.outputDirectory = dir; } public void setDestdir(File dir) { doc.outputDirectory = dir; } public void setCustomLess(String name) { customLessFile = name; } public void setPrettifyTheme(String value) { doc.prettifyTheme = value; } public void setMinify(boolean value) { doc.minify = value; } public void setGoogleAnalyticsid(String value) { doc.googleAnalyticsId = value; } public void setGooglePlusid(String value) { doc.googlePlusId = value; } public void setGooglePlusOne(boolean value) { doc.googlePlusOne = value; } public void setResponsiveLayout(boolean value) { doc.isResponsiveLayout = value; } public void setRssfeed(String feed) { doc.rssFeed = feed; } public void setAtomfeed(String feed) { doc.atomFeed = feed; } protected void setToken(String token, Object value) { if (value == null) { value = "${" + token + "}"; } createReplace().set("${" + token + "}", value); } @Override public void execute() throws BuildException { Build build = getBuild(); // automatically setup substitution tokens setToken(Toolkit.Key.name.projectId(), build.getPom().name); setToken(Toolkit.Key.description.projectId(), build.getPom().description); setToken(Toolkit.Key.url.projectId(), build.getPom().url); setToken(Toolkit.Key.issuesUrl.projectId(), build.getPom().issuesUrl); setToken(Toolkit.Key.inceptionYear.projectId(), build.getPom().inceptionYear); setToken(Toolkit.Key.organization.projectId(), build.getPom().organization); setToken(Toolkit.Key.organizationUrl.projectId(), build.getPom().organizationUrl); setToken(Toolkit.Key.forumUrl.projectId(), build.getPom().forumUrl); setToken(Toolkit.Key.socialNetworkUrl.projectId(), build.getPom().socialNetworkUrl); setToken(Toolkit.Key.blogUrl.projectId(), build.getPom().blogUrl); setToken(Toolkit.Key.ciUrl.projectId(), build.getPom().ciUrl); setToken(Toolkit.Key.mavenUrl.projectId(), build.getPom().mavenUrl); if (build.getPom().scm != null) { setToken(Key.scmUrl.projectId(), build.getPom().scm.url); } setToken(Toolkit.Key.groupId.projectId(), build.getPom().groupId); setToken(Toolkit.Key.artifactId.projectId(), build.getPom().artifactId); setToken(Toolkit.Key.version.projectId(), build.getPom().version); setToken(Toolkit.Key.coordinates.projectId(), build.getPom().getCoordinates()); setToken(Toolkit.Key.buildDate.projectId(), build.getBuildDateString()); setToken(Toolkit.Key.buildTimestamp.projectId(), build.getBuildTimestamp()); setToken(Toolkit.Key.releaseVersion.projectId(), build.getPom().releaseVersion); setToken(Toolkit.Key.releaseDate.projectId(), build.getReleaseDateString()); setToken(Toolkit.Key.releaseDate.referenceId(), build.getReleaseDate()); setToken(Toolkit.Key.buildDate.referenceId(), build.getBuildDate()); for (Map.Entry<String, String> entry : build.getPom().getProperties().entrySet()) { setToken(entry.getKey(), entry.getValue()); } if (doc.name == null) { doc.name = build.getPom().name; } if (doc.sourceDirectory == null) { doc.sourceDirectory = build.getConfig().getSiteSourceDirectory(); } if (doc.outputDirectory == null) { doc.outputDirectory = build.getConfig().getSiteTargetDirectory(); } if (doc.templateDirectory == null) { doc.templateDirectory = new File(build.getConfig().getSiteSourceDirectory(), "templates"); } doc.customLessFile = findFile(customLessFile); if (logo != null) { doc.logo = findFile(logo.getFile()); } if (favicon != null) { doc.favicon = findFile(favicon.getFile()); } titleClass(build.getPom().name); loadRuntimeDependencies(build, new Dependency("mx:pegdown"), new Dependency("mx:wikitext-core"), new Dependency("mx:wikitext-twiki"), new Dependency("mx:wikitext-textile"), new Dependency("mx:wikitext-tracwiki"), new Dependency("mx:wikitext-mediawiki"), new Dependency("mx:wikitext-confluence"), new Dependency("mx:freemarker")); Dependency bootstrap = new Dependency("mx:bootstrap"); Dependency jquery = new Dependency("mx:jquery"); Dependency d3js = new Dependency("mx:d3js"); Dependency prettify = new Dependency("mx:prettify"); Dependency less = new Dependency("mx:lesscss-engine"); loadRuntimeDependencies(build, bootstrap, jquery, d3js, prettify, less); if (doc.outputDirectory.exists()) { FileUtils.delete(doc.outputDirectory); } doc.outputDirectory.mkdirs(); if (doc.logo != null && doc.logo.exists()) { try { FileUtils.copy(doc.outputDirectory, doc.logo); } catch (IOException e) { getConsole().error(e, "Failed to copy logo file!"); } } if (doc.favicon != null && doc.favicon.exists()) { try { FileUtils.copy(doc.outputDirectory, doc.favicon); } catch (IOException e) { getConsole().error(e, "Failed to copy favicon file!"); } } extractBootstrap(bootstrap, less, doc.outputDirectory); extractJQuery(jquery, doc.outputDirectory); extractD3js(d3js, doc.outputDirectory); extractPrettify(prettify, doc.outputDirectory); // setup prev/next pager links preparePages(doc.structure.elements); for (DocPage page : doc.freeformPages) { // process out-of-structure template pages prepareTemplatePage(page); } // block directives createNomarkdown().configure("---NOMARKDOWN---", false, false); createNomarkdown().configure("---ESCAPE---", true, false); createNomarkdown().configure("---FIXED---", true, true); createNomarkdown().configure("```", true, true); doc.nomarkdowns.add(new ExcludeText("---EXCLUDE---")); // wikitext transform directives createWikiText().configureSyntax(Syntax.TWIKI); createWikiText().configureSyntax(Syntax.TEXTILE); createWikiText().configureSyntax(Syntax.TRACWIKI); createWikiText().configureSyntax(Syntax.MEDIAWIKI); createWikiText().configureSyntax(Syntax.CONFLUENCE); // language syntax highlighting directives createNomarkdown().configureLanguage("---CODE---", null); createNomarkdown().configureLanguage("---CSS---", "lang-css"); createNomarkdown().configureLanguage("---JAVA---", "lang-java"); createNomarkdown().configureLanguage("---JSON---", "lang-json"); createNomarkdown().configureLanguage("---SQL---", "lang-sql"); createNomarkdown().configureLanguage("---WIKI---", "lang-wiki"); createNomarkdown().configureLanguage("---XML---", "lang-xml"); createNomarkdown().configureLanguage("---YAML---", "lang-yaml"); Docs.execute(build, doc, isVerbose()); writeDependenciesAsJson(); // add site resource directories for (File dir : build.getConfig().getResourceDirectories(Scope.site)) { createResource().createFileset().setDir(dir); } for (org.moxie.Resource resource : resources) { File destdir = doc.outputDirectory; if (!StringUtils.isEmpty(resource.prefix)) { destdir = new File(doc.outputDirectory, resource.prefix); } try { if (resource.file != null) { FileUtils.copy(destdir, resource.file); } else { for (FileSet fs : resource.filesets) { DirectoryScanner ds = fs.getDirectoryScanner(getProject()); File fromDir = fs.getDir(getProject()); for (String srcFile : ds.getIncludedFiles()) { File dir = destdir; if (srcFile.indexOf(File.separatorChar) > -1) { dir = new File(dir, srcFile.substring(0, srcFile.lastIndexOf(File.separatorChar))); } File file = new File(fromDir, srcFile); FileUtils.copy(dir, file); } } } } catch (Exception e) { e.printStackTrace(); } } } protected void preparePages(List<DocElement> elements) { for (DocElement element : elements) { if (element instanceof DocMenu) { DocMenu menu = (DocMenu) element; for (int i = 0, maxIndex = menu.elements.size() - 1; i <= maxIndex; i++) { DocElement subElement = menu.elements.get(i); if (subElement instanceof DocPage) { prepareTemplatePage((DocPage) subElement); if (menu.showPager) { // link to previous page DocPage page = (DocPage) subElement; DocElement prev = i == 0 ? null : menu.elements.get(i - 1); if (prev != null && prev instanceof DocPage) { page.prevPage = (DocPage) prev; } // link to next page DocElement next = i == maxIndex ? null : menu.elements.get(i + 1); if (next != null && next instanceof DocPage) { page.nextPage = (DocPage) next; } // show pager is dependent on having at least a prev or next page.showPager = page.prevPage != null || page.nextPage != null; page.pagerLayout = menu.pagerLayout; page.pagerPlacement = menu.pagerPlacement; } } else if (subElement instanceof DocMenu) { // process menu/submenu preparePages(Arrays.asList(subElement)); } } } else if (element instanceof DocPage) { // pages which are generated from a Freemarker template // process navbar items prepareTemplatePage((DocPage) element); } } } protected File findFile(String filename) { if (!StringUtils.isEmpty(filename)) { List<File> dirs = new ArrayList<File>(); dirs.add(null); // dir specified in filename dirs.add(getBuild().getConfig().getSiteSourceDirectory()); dirs.addAll(getBuild().getConfig().getResourceDirectories(Scope.site)); for (File dir : dirs) { File aFile = new File(dir, filename); if (aFile.exists()) { return aFile; } } } return null; } protected void extractBootstrap(Dependency bsDep, Dependency lessDep, File outputFolder) { getConsole().debug("injecting Twitter Bootstrap"); String wj = MessageFormat.format("META-INF/resources/webjars/{0}/{1}/", bsDep.artifactId, bsDep.version); extractResource(outputFolder, wj + "js/bootstrap.min.js", "bootstrap/js/bootstrap.min.js", true); extractResource(outputFolder, wj + "css/bootstrap-responsive.min.css", "bootstrap/css/bootstrap-responsive.min.css", true); extractResource(outputFolder, wj + "img/glyphicons-halflings.png", "bootstrap/img/glyphicons-halflings.png", true); extractResource(outputFolder, wj + "img/glyphicons-halflings-white.png", "bootstrap/img/glyphicons-halflings-white.png", true); String [] modules = { "accordion", "alerts", "bootstrap", "breadcrumbs", "button-groups", "buttons", "carousel", "close", "code", "component-animations", "dropdowns", "forms", "grid", "hero-unit", "labels-badges", "layouts", "media", "mixins", "modals", "navbar", "navs", "pager", "pagination", "popovers", "progress-bars", "reset", "scaffolding", "sprites", "tables", "thumbnails", "tooltip", "type", "utilities", "variables", "wells" }; for (String module : modules) { extractResource(outputFolder, wj + "less/" + module + ".less", "bootstrap/less/" + module + ".less", true); } // extract Moxie's Bootstrap overrides extractResource(outputFolder, "moxie.less"); File bsLess = new File(outputFolder, "bootstrap/less/bootstrap.less"); String content = FileUtils.readContent(bsLess, "\n"); content += "\n" + FileUtils.readContent(new File(outputFolder, "moxie.less"), "\n"); if (doc.customLessFile != null && doc.customLessFile.exists()) { content += "\n" + FileUtils.readContent(doc.customLessFile, "\n"); } FileUtils.writeContent(bsLess, content); Build build = getBuild(); loadRuntimeDependencies(build, new Dependency("mx:rhino")); // compile Bootstrap and custom.less overrides into css try { File bsCss = new File(outputFolder, "bootstrap/css/bootstrap.css"); if (doc.minify) { getConsole().log("compiling and minifying {0}...", bsCss.getAbsolutePath()); } else { getConsole().log("compiling {0}...", bsCss.getAbsolutePath()); } long start = System.currentTimeMillis(); String css = LessUtils.compile(bsLess, doc.minify); FileUtils.writeContent(bsCss, css); getConsole().log("css generated in {0} msecs", System.currentTimeMillis() - start); } catch (Exception e) { getConsole().error(e, "Failed to compile LESS!"); } // remove less folder FileUtils.delete(new File(outputFolder, "bootstrap/less")); } protected void extractJQuery(Dependency dep, File outputFolder) { getConsole().debug("injecting JQuery"); String wj = MessageFormat.format("META-INF/resources/webjars/{0}/{1}/", dep.artifactId, dep.version); extractResource(outputFolder, wj + "jquery.min.js", "bootstrap/js/jquery.js", true); } protected void extractD3js(Dependency dep, File outputFolder) { getConsole().debug("injecting D3"); String wj = MessageFormat.format("META-INF/resources/webjars/{0}/{1}/", dep.artifactId, dep.version); extractResource(outputFolder, wj + "d3.v2.min.js", "d3/d3.js", true); extractResource(outputFolder, "d3/rings.css"); extractResource(outputFolder, "d3/rings.js"); } protected void extractPrettify(Dependency dep, File outputFolder) { getConsole().debug("injecting GoogleCode Prettify"); String wj = MessageFormat.format("META-INF/resources/webjars/{0}/{1}/", dep.artifactId, dep.version); extractResource(outputFolder, wj + "prettify.js", "prettify/prettify.js", true); extractResource(outputFolder, wj + "prettify.css", "prettify/prettify.css", true); String [] langs = { "apollo", "clj", "css", "go", "hs", "lisp", "lua", "ml", "n", "proto", "scala", "sql", "tex", "vb", "vhdl", "wiki", "xq", "yaml" }; for (String lang : langs) { extractResource(outputFolder, wj + "lang-" + lang + ".js", "prettify/lang-" + lang + ".js", true); } extractZippedResource("prettify-themes.zip", outputFolder, "prettify"); } protected void extractZippedResource(String zipResource, File outputDirectory, String toDir) { try { ZipInputStream is = new ZipInputStream(getClass().getResourceAsStream("/" + zipResource)); File destFolder = new File(outputDirectory, toDir); destFolder.mkdirs(); ZipEntry entry = null; while ((entry = is.getNextEntry()) != null) { if (entry.isDirectory()) { File file = new File(destFolder, entry.getName()); file.mkdirs(); continue; } FileOutputStream os = new FileOutputStream(new File(destFolder, entry.getName())); byte[] buffer = new byte[32767]; int len = 0; while ((len = is.read(buffer)) > -1) { os.write(buffer, 0, len); } os.close(); is.closeEntry(); } is.close(); } catch (Exception e) { e.printStackTrace(); } } protected void deleteResource(File baseFolder, String file) { new File(baseFolder, file).delete(); } protected void prepareTemplatePage(DocPage page) { // pages which are generated from a Freemarker template if (StringUtils.isEmpty(page.src) && page.templates != null && page.templates.size() == 1) { String token = "%-" + page.as + "%"; page.templates.get(0).setToken(token); page.content = token; } } void writeDependenciesAsJson() { DepNode root = new DepNode(new Dependency(getBuild().getPom().getCoordinates())); Set<Dependency> dependencies = getBuild().getSolver().getDependencies(Scope.test); DepNode currRoot = root; for (Dependency dep : dependencies) { if (currRoot.dep.ring == dep.ring) { // dep is at same ring as curr root, add to parent currRoot = currRoot.parent.add(dep); } else if (dep.ring > currRoot.dep.ring) { // dep is one ring lower then curr root, add to curr root // and reset curr root currRoot = currRoot.add(dep); } else if (dep.ring < currRoot.dep.ring) { // find the parent node for this dep currRoot = currRoot.parentAt(dep.ring - 1); // add dep to the parent node currRoot = currRoot.add(dep); } } String json = root.asJSON(); File file = new File(getBuild().getConfig().getSiteTargetDirectory(), "moxie-dependencies.json"); FileUtils.writeContent(file, json); } private class DepNode { DepNode parent; Dependency dep; List<DepNode> children; DepNode(Dependency dep) { this(dep, null); } DepNode(Dependency dep, DepNode parent) { this.dep = dep; this.parent = parent; children = new ArrayList<DepNode>(); } DepNode add(Dependency dep) { DepNode node = new DepNode(dep, this); children.add(node); return node; } DepNode parentAt(int ring) { DepNode node = this; while (node != null && node.dep.ring >= ring) { if (node.parent == null) { break; } node = node.parent; } return node; } String asJSON() { StringBuilder sb = new StringBuilder("{\n"); sb.append(MessageFormat.format(" \"ring\" : \"{0}\",\n", dep.ring)); sb.append(MessageFormat.format(" \"name\" : \"{0} {1}\"", dep.artifactId, dep.version)); if (children.size() == 0) { //sb.append(MessageFormat.format(",\n \"colour\" : \"{0}\"", "#")); } else if (children.size() > 0) { sb.append(",\n \"children\" : [\n"); for (DepNode node : children) { sb.append(StringUtils.insertSoftTab(node.asJSON())); sb.append(",\n"); } // trim trailing comma,newline sb.setLength(sb.length() - 2); sb.append("\n ]\n"); } sb.append("\n}\n"); return sb.toString(); } } }