/atlassian-docco/src/main/java/com/atlassian/docco/Docco.java
Java | 492 lines | 327 code | 60 blank | 105 comment | 45 complexity | 66819d6de5461e42462f5a2bc181645a MD5 | raw file
- /*!
- This is the main class for parsing docco comments and generating [DoccoParts](${basePath}/src/main/java/com/atlassian/docco/DoccoParts.java.html) that can be used to generate docco html.
- Although this class has methods for actually generating a single html file, it's more common to use [DoccoBatch](${basePath}/src/main/java/com/atlassian/docco/DoccoBatch.java.html) to do the html generation over a batch of files.
- To construct a Docco object you could call the constructor in this class, but it is easier and preferred to use the [DoccoBuilder](${basePath}/src/main/java/com/atlassian/docco/builder/DoccoBuilder.java.html).
- **Note:** You'll only need to create a Docco object if you're not using DoccoBatch.
- */
- package com.atlassian.docco;
- import java.io.*;
- import java.net.URISyntaxException;
- import java.net.URL;
- import java.util.List;
- import java.util.Map;
- import java.util.regex.Matcher;
- import com.atlassian.docco.builder.DoccoBuilder;
- import com.atlassian.docco.mapping.DoccoCommentPatterns;
- import com.atlassian.docco.mapping.DoccoFileMappingManager;
- import com.google.template.soy.SoyFileSet;
- import com.google.template.soy.data.SanitizedContent;
- import com.google.template.soy.data.SoyListData;
- import com.google.template.soy.data.SoyMapData;
- import com.google.template.soy.tofu.SoyTofu;
- import org.apache.commons.io.FileUtils;
- import org.apache.commons.lang.StringUtils;
- import org.pegdown.PegDownProcessor;
- import static com.google.common.base.Preconditions.checkNotNull;
- import static com.google.common.base.Preconditions.checkState;
- public final class Docco
- {
- private final boolean stripJavadoc;
- private final boolean includeDefaultResources;
- private final DoccoFileMappingManager fileMappings;
- private final PegDownProcessor pegDown;
- private final List<File> customResources;
- private final SoyTofu horizontalTofu;
- private final SoyTofu verticalTofu;
- /*!####Construct a Docco object.
- This is usually called by the [DoccoBuilder](${basePath}/src/main/java/com/atlassian/docco/builder/DoccoBuilder.java.html) instead of being invoked directly.
- */
- public Docco(URL horizonatlSoyTemplate, URL verticalSoyTemplate, DoccoFileMappingManager fileMappings, boolean stripJavadoc, boolean includeDefaultResources, List<File> customResources, PegDownProcessor pegDown)
- {
- this.fileMappings = fileMappings;
- this.stripJavadoc = stripJavadoc;
- this.pegDown = pegDown;
- this.includeDefaultResources = includeDefaultResources;
- this.customResources = customResources;
- /*!####ToFu Anyone?
- Here we create the soyTofu compilers for both horizontal and vertical displays so we can reuse them when making multiple calls to render the html.
- */
- SoyFileSet soyFileSetH = new SoyFileSet.Builder().add(horizonatlSoyTemplate).build();
- this.horizontalTofu = soyFileSetH.compileToJavaObj().forNamespace("atlassian.docco");
- SoyFileSet soyFileSetV = new SoyFileSet.Builder().add(verticalSoyTemplate).build();
- this.verticalTofu = soyFileSetV.compileToJavaObj().forNamespace("atlassian.docco");
- }
- /*!####Get the DoccoParts
- Given a source file, this will return a [DoccoParts](${basePath}/src/main/java/com/atlassian/docco/DoccoParts.java.html) object representing the file.
- */
- public DoccoParts getDoccoParts(File sourceFile) throws IOException
- {
- checkNotNull(sourceFile);
- checkState(sourceFile.exists(),"Source file doesn't exist!");
- DoccoParts doccoParts = new DoccoParts(pegDown);
- DoccoCommentPatterns patterns = fileMappings.getPatternsForFile(sourceFile);
- boolean forceJavadoc = fileMappings.getForceKeepJavadocForFile(sourceFile);
- try
- {
- BufferedReader buff = new BufferedReader(new FileReader(sourceFile));
- String line;
- Section section = new Section(pegDown);
- String doc = "";
- String indent = "";
- String groups = "";
- boolean insideJavadoc = false;
- boolean insideDocco = false;
- boolean foundMatch = false;
- boolean pastHeader = false;
- /*!####Read The File
- After setting up the DoccoParts container and the comment mappings, we're reay to read the file line by line.
- */
- while((line = buff.readLine()) != null)
- {
- foundMatch = false;
- /*!
- First we test for a special comment that can add the file to multiple "groups" to be used as custom index navigation headers.
- A group comment looks like:
- \/*!! some groupname, some other groupname *\/
- *notice the double __!!__ *
- The group comment must be the first thing in the file
- */
- Matcher groupLine = patterns.getGroupsPattern().matcher(line);
- if(groupLine.matches() && !insideJavadoc && !pastHeader)
- {
- foundMatch = true;
- groups = groupLine.group(1);
- if(!pastHeader)
- {
- String[] groupNames = StringUtils.split(groups,",");
- if(null != groupNames && groupNames.length > 0)
- {
- for(String groupName : groupNames)
- {
- doccoParts.addGroup(StringUtils.trim(groupName));
- }
- }
- }
- insideDocco = false;
- continue;
- }
- /*!
- Next we test for a single line comment. If we find one and it appears before any other code in the file, we treat it as a header, if it appears after some other code, we start a new section for it.
- We also do a check to see if the comment starts with a **-** character, and if it does, we mark it as "hidden" so the template can choose to collapse it.
- A single line comment looks like:
- \/*! some comment in here *\/
- *notice the single __!__ *
- */
- Matcher singleLine = patterns.getSingleLinePattern().matcher(line);
- if(singleLine.matches() && !insideJavadoc)
- {
- foundMatch = true;
- doc = StringUtils.replace(StringUtils.replace(singleLine.group(1),"\\/*","/*"),"*\\/","*/");
- if(!pastHeader)
- {
- doccoParts.getHeader().addDoc(doc);
- pastHeader = true;
- }
- else
- {
- section = flushSection(section,doccoParts);
- if(doc.startsWith("-"))
- {
- section.setHidden(true);
- section.setTitle(doc.substring(1));
- }
- else
- {
- section.addDoc(doc);
- }
- }
- insideDocco = false;
- continue;
- }
- /*!
- Next we test for the start of a multi-line comment.
- This follows the same header/section/hidden rules as the single line comment
- A multi-line start comment looks like:
- \/*! some comment
- *notice the single __!__ *
- */
- Matcher multiStart = patterns.getMultilineBeginPattern().matcher(line);
- if(multiStart.matches())
- {
- foundMatch = true;
- indent = multiStart.group(1);
- doc = StringUtils.replace(StringUtils.replace(multiStart.group(2),"\\/*","/*"),"*\\/","*/");
- if(!pastHeader)
- {
- doccoParts.getHeader().addDoc(doc);
- }
- else
- {
- section = flushSection(section,doccoParts);
- if(doc.startsWith("-"))
- {
- section.setHidden(true);
- section.setTitle(doc.substring(1));
- }
- else
- {
- section.addDoc(doc);
- }
- }
- insideDocco = true;
- continue;
- }
- /*!
- Check for a "normal" javadoc style comment. If we find one and we're not supposed to strip it, we add it to the code
- */
- Matcher javadoc = patterns.getJavadocBeginPattern().matcher(line);
- if(javadoc.matches())
- {
- foundMatch = true;
- insideJavadoc = true;
- pastHeader = true;
- if(!stripJavadoc || forceJavadoc)
- {
- section.addCode(line);
- }
- continue;
- }
- /*!
- When we find the end of a multi-line comment, we need to see if we're processing a docco comment or a java doc comment.
- If it's a docco comment, we also check to see if we're a header or not, then either add the doc to the header or the current section.
- If it's a javadoc and we're not stripping it, we add it to the code.
- */
- Matcher multiEnd = patterns.getMultilineEndPattern().matcher(line);
- if(multiEnd.matches())
- {
- foundMatch = true;
- if(insideDocco)
- {
- doc = StringUtils.replace(StringUtils.replace(multiEnd.group(1),"\\/*","/*"),"*\\/","*/");
- if(!pastHeader)
- {
- doccoParts.getHeader().addDoc(doc);
- pastHeader = true;
- }
- else
- {
- section.addDoc(doc);
- }
- }
- if(insideJavadoc && (!stripJavadoc || forceJavadoc))
- {
- section.addCode(line);
- }
- insideDocco = false;
- insideJavadoc = false;
- continue;
- }
- /*!
- Finally, if none of the above match, we determine if we're inside a header or docco comment and add the appropriate docs, or we add the line to the section's code.
- */
- if(!foundMatch)
- {
- if(insideDocco)
- {
- doc = StringUtils.replace(StringUtils.replace(line,"\\/*","/*"),"*\\/","*/");
- if(!pastHeader)
- {
- doccoParts.getHeader().addDoc(doc);
- }
- else
- {
- section.addDoc(doc.replaceAll("^" + indent,""));
- }
- }
- else if(!insideJavadoc || (!stripJavadoc || forceJavadoc))
- {
- if(pastHeader)
- {
- section.addCode(line);
- }
- else if (StringUtils.isNotBlank(line))
- {
- pastHeader = true;
- section.addCode(line);
- }
- }
- }
- }
- flushSection(section,doccoParts);
- }
- catch (FileNotFoundException e)
- {
- //this should never happen since we have a precondition
- e.printStackTrace();
- }
- /*!####Add the Default Group
- If a group comment wasn't found, we add the file's directory as the group
- */
- if(doccoParts.getGroups().isEmpty())
- {
- doccoParts.addGroup("file://" + sourceFile.getParentFile().getCanonicalPath());
- }
- return doccoParts;
- }
- /*!####Flushing Sections
- This simply creates a new section if the current section has both docs and code or returns the current section
- */
- private Section flushSection(Section section, DoccoParts doccoParts)
- {
- if(section.isCommitted())
- {
- if(!section.isEmpty())
- {
- doccoParts.addSection(section);
- }
- return new Section(pegDown);
- }
- return section;
- }
- /*!####Render Time
- Writes the docco html for a given source file to the given output path.
- This will create two directories under the output path named "horizontal" and "vertical" to hold the respective docco layouts of the source file html.
- This will use the google closure templates to render the html and copy any required css/js resources
- */
- public void writeHtml(File sourceFile, File outputFile) throws IOException
- {
- checkNotNull(sourceFile);
- checkState(sourceFile.exists(),"Source file doesn't exist!");
- checkNotNull(outputFile);
- File hBase = new File(outputFile.getParentFile(),"horizontal");
- FileUtils.forceMkdir(hBase);
- File hFile = new File(hBase,outputFile.getName());
- File vBase = new File(outputFile.getParentFile(),"vertical");
- FileUtils.forceMkdir(vBase);
- File vFile = new File(vBase,outputFile.getName());
- String syntax = fileMappings.getSyntaxForFile(sourceFile);
- DoccoParts parts = getDoccoParts(sourceFile);
- SanitizedContent header = new SanitizedContent(parts.getHeader().getDoc(), SanitizedContent.ContentKind.HTML);
- String fileNoExt = StringUtils.substringBeforeLast(sourceFile.getName(),".");
- SoyMapData soyData = new SoyMapData("basePath", ".", "title",sourceFile.getName(),"nameWithoutExtension",fileNoExt,"syntax", syntax, "header",header,"sections",Docco.getSoySectionData(parts.getSections()));
- String renderedH = horizontalTofu.newRenderer(".singlePage").setData(soyData).render();
- FileUtils.writeStringToFile(hFile,renderedH);
- String renderedV = verticalTofu.newRenderer(".singlePage").setData(soyData).render();
- FileUtils.writeStringToFile(vFile,renderedV);
- copyResources(hBase,vBase);
- }
- /*!
- A simple wrapper that uses the source file's absolute path as the output folder
- */
- public void writeHtml(File sourceFile) throws IOException
- {
- writeHtml(sourceFile, new File(sourceFile.getAbsolutePath() + ".html"));
- }
- /*!
- Copies all the resources needed for the html display.
- This will copy the default packaged resources if includeDefaultResources = true (the default) and then it will copy any custom resources provided
- */
- public void copyResources(File hBase, File vBase) throws IOException
- {
- if(includeDefaultResources)
- {
- copyDefaultResources(hBase, vBase);
- }
- for(File file : customResources)
- {
- if(file.isDirectory())
- {
- FileUtils.copyDirectory(file,hBase);
- FileUtils.copyDirectory(file,vBase);
- }
- else if(file.isFile())
- {
- FileUtils.copyFile(file, new File(hBase, file.getName()));
- FileUtils.copyFile(file, new File(vBase, file.getName()));
- }
- }
- }
- /*!- copy default resources */
- public void copyDefaultResources(File hBase, File vBase) throws IOException
- {
- //horizontal
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap.min.css"),new File(hBase,"css/bootstrap.min.css"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap-responsive.min.css"),new File(hBase,"css/bootstrap-responsive.min.css"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/atlassian-docco-h.css"),new File(hBase,"css/atlassian-docco.css"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/atlassian-docco-h.js"),new File(hBase,"js/atlassian-docco.js"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/bootstrap.min.js"),new File(hBase,"js/bootstrap.min.js"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/jquery.cookie.js"),new File(hBase,"js/jquery.cookie.js"));
- //vertical
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap.min.css"),new File(vBase,"css/bootstrap.min.css"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap-responsive.min.css"),new File(vBase,"css/bootstrap-responsive.min.css"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/atlassian-docco-v.css"),new File(vBase,"css/atlassian-docco.css"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/atlassian-docco-v.js"),new File(vBase,"js/atlassian-docco.js"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/bootstrap.min.js"),new File(vBase,"js/bootstrap.min.js"));
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/jquery.cookie.js"),new File(vBase,"js/jquery.cookie.js"));
- }
- /*!- getters for default templates */
- public static File getDefaultTemplate() throws URISyntaxException
- {
- return new File(Docco.class.getResource("/template/atlassian-docco-h.soy").toURI());
- }
- public static URL getDefaultHorizontalTemplate() throws URISyntaxException
- {
- URL url = Docco.class.getResource("/template/atlassian-docco-h.soy");
- //return new File(url.toURI());
- return url;
- }
- public static URL getDefaultVerticalTemplate() throws URISyntaxException
- {
- URL url = Docco.class.getResource("/template/atlassian-docco-v.soy");
- //return new File(url.toURI());
- return url;
- }
- /*!
- Converts a list of docco [Section](${basePath}/src/main/java/com/atlassian/docco/Section.java.html) objects to a SoyListData object that gets passed to the soy templates.
- */
- public static SoyListData getSoySectionData(List<Section> sections) throws IOException
- {
- SoyListData soyList = new SoyListData();
- for(Section section : sections)
- {
- SanitizedContent doc = new SanitizedContent(section.getDoc(), SanitizedContent.ContentKind.HTML);
- SanitizedContent code = new SanitizedContent(section.getCode(), SanitizedContent.ContentKind.HTML);
- SoyMapData soyMap = new SoyMapData("doc",doc,"code",section.getCode(),"hidden", section.isHidden(),"title",section.getTitle());
- soyList.add(soyMap);
- }
- return soyList;
- }
- /*!
- Does the same as *getSoySectionData* but accepts a map of name/value pairs to be used as token replacements in docco comments.
- This is typically used to make linking easier by supplying the **basePath** to the root folder as a token replacement.
- */
- public static SoyListData getSoySectionDataWithTokenReplacement(List<Section> sections,Map<String,String> replaceMap) throws IOException
- {
- SoyListData soyList = new SoyListData();
- for(Section section : sections)
- {
- SanitizedContent doc = new SanitizedContent(section.getDocWithTokenReplacement(replaceMap), SanitizedContent.ContentKind.HTML);
- SanitizedContent code = new SanitizedContent(section.getCode(), SanitizedContent.ContentKind.HTML);
- SoyMapData soyMap = new SoyMapData("doc",doc,"code",section.getCode(),"hidden", section.isHidden(),"title",section.getTitle());
- soyList.add(soyMap);
- }
- return soyList;
- }
- /*!- convenience method to get builder */
- public static DoccoBuilder builder()
- {
- return new DoccoBuilder();
- }
- }