eu.trentorise.opendata.josman.JosmanProject.java Source code

Java tutorial

Introduction

Here is the source code for eu.trentorise.opendata.josman.JosmanProject.java

Source

package eu.trentorise.opendata.josman;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import eu.trentorise.opendata.commons.NotFoundException;
import eu.trentorise.opendata.commons.TodUtils;
import static eu.trentorise.opendata.commons.TodUtils.checkNotEmpty;
import eu.trentorise.opendata.commons.SemVersion;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import jodd.jerry.Jerry;
import jodd.jerry.JerryFunction;
import org.apache.commons.io.FileUtils;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.eclipse.egit.github.core.RepositoryTag;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.SortedMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.DepthWalk;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.parboiled.common.ImmutableList;
import org.pegdown.Parser;
import org.pegdown.PegDownProcessor;

/**
 * Represents a Josman project, holding information about maven and git repository.
 * @author David Leoni
 */
public class JosmanProject {

    private static final Logger LOG = Logger.getLogger(JosmanProject.class.getName());
    private static final int DEPTH = 8000;

    /**
     * Folder in source code where user documentation is held.
     */
    public static final String DOCS_FOLDER = "docs";

    public static final String README_MD = "README.md";
    public static final String CHANGES_MD = "CHANGES.md";

    private String repoName;
    private String repoTitle;
    private String repoOrganization;
    private boolean snapshotMode;
    private File sourceRepoDir;
    private File pagesDir;
    private ImmutableList<SemVersion> ignoredVersions;

    private Model pom;
    private Repository repo;

    /**
     * Null means tags were not fetched. Notice we may also have fetched tags
     * and discovered there where none, so there might also be an empty array.
     */
    @Nullable
    private ImmutableList<RepositoryTag> repoTags;

    PegDownProcessor pegDownProcessor;

    private static void deleteOutputVersionDir(File outputVersionDir, int major, int minor) {

        String versionName = "" + major + "." + minor;

        if (outputVersionDir.exists()) {
            // let's be strict before doing moronic things
            checkArgument(major >= 0);
            checkArgument(minor >= 0);
            if (outputVersionDir.getAbsolutePath().endsWith(versionName)) {
                LOG.info("Found already existing output dir, cleaning it...");
                try {
                    FileUtils.deleteDirectory(outputVersionDir);
                    LOG.info("Done cleaning directory.");
                } catch (Exception ex) {
                    throw new RuntimeException("Error while deleting directory!", ex);
                }
            } else {
                throw new RuntimeException("output path " + outputVersionDir.getAbsolutePath()
                        + " doesn't end with '" + versionName + "', avoiding cleaning it for safety reasons!");
            }
        }

    }

    /**
     *
     * @param snapshotMode if true the website generator will only process the
     * current branch snapshot. Otherwise all released versions except
     * {@code ignoredVersions} will be processed,
     *
     */
    public JosmanProject(String repoName, String repoTitle, String repoOrganization, String sourceRepoDirPath,
            String pagesDirPath, List<SemVersion> ignoredVersions, boolean snapshotMode) {

        checkNotEmpty(repoName, "Invalid repository name!");
        checkNotEmpty(repoTitle, "Invalid repository title!");
        checkNotEmpty(repoOrganization, "Invalid repository organization!");
        checkNotNull(sourceRepoDirPath, "Invalid repository source docs dir path!");
        checkNotNull(pagesDirPath, "Invalid pages dir path!");
        checkNotNull(ignoredVersions, "Invalid versions to ignore!");

        this.repoName = repoName;
        this.repoTitle = repoTitle;
        this.repoOrganization = repoOrganization;
        this.ignoredVersions = ImmutableList.copyOf(ignoredVersions);
        if (sourceRepoDirPath.isEmpty()) {
            this.sourceRepoDir = new File("." + File.separator);
        } else {
            this.sourceRepoDir = new File(sourceRepoDirPath);
        }
        this.snapshotMode = snapshotMode;
        this.pagesDir = new File(pagesDirPath);
        checkArgument(!sourceRepoDir.getAbsolutePath().equals(pagesDir.getAbsolutePath()),
                "Source folder and target folder coincide! They are " + sourceRepoDir.getAbsolutePath());
        this.pegDownProcessor = new PegDownProcessor(Parser.QUOTES | Parser.HARDWRAPS | Parser.AUTOLINKS
                | Parser.TABLES | Parser.FENCED_CODE_BLOCKS | Parser.WIKILINKS | Parser.STRIKETHROUGH // not supported in netbeans flow 2.0 yet
                | Parser.ANCHORLINKS // not supported in netbeans flow 2.0 yet      
        );
    }

    private File sourceDocsDir() {
        return new File(sourceRepoDir, DOCS_FOLDER);
    }

    private File targetJavadocDir(SemVersion version) {
        return new File(targetVersionDir(version), "javadoc");
    }

    private File sourceJavadocDir(SemVersion version) {
        if (snapshotMode) {
            return new File(sourceRepoDir, "target" + File.separator + "apidocs");
        } else {
            throw new UnsupportedOperationException("todo non-local javadoc not supported yet");
        }
    }

    static String programLogoName(String repoName) {
        return repoName + "-logo-200px.png";
    }

    static File programLogo(File sourceDocsDir, String repoName) {
        return new File(sourceDocsDir, "img" + File.separator + programLogoName(repoName));
    }

    /**
     * Copies provided stream to destination, which is determined according to
     * {@code relPath}, {@code preprendedPath} and {@code version}
     *
     * @param sourceStream
     * @param relPath path relative to root, i.e. docs/README.md or
     * img/mypic.jpg
     *
     * @param version
     * @return the target file
     */
    File copyStream(InputStream sourceStream, String relPath, final SemVersion version, List<String> relPaths) {

        checkNotNull(sourceStream, "Invalid source stream!");
        checkNotEmpty(relPath, "Invalid relative path!");
        checkNotEmpty(relPaths, "Invalid relative paths!");
        checkNotNull(version);

        File targetFile = Josmans.targetFile(pagesDir, relPath, version);

        if (targetFile.exists()) {
            throw new RuntimeException("Target file already exists! " + targetFile.getAbsolutePath());
        }

        if (relPath.endsWith(".md")) {

            LOG.log(Level.INFO, "Creating file {0}", targetFile.getAbsolutePath());
            if (targetFile.exists()) {
                throw new RuntimeException("Target file already exists! Target is " + targetFile.getAbsolutePath());
            }
            copyMdAsHtml(sourceStream, relPath, version, relPaths);
        } else {

            LOG.log(Level.INFO, "Copying file into {0}", targetFile.getAbsolutePath());

            try {
                FileUtils.copyInputStreamToFile(sourceStream, targetFile);
                sourceStream.close();
                LOG.info("Done copying file.");
            } catch (Exception ex) {
                throw new RuntimeException("Error while copying stream to file!", ex);
            }
        }
        return targetFile;
    }

    /**
     * Writes an md stream as html to outputFile
     *
     * @param outputFile Must not exist. Eventual needed directories in the path
     * will be created
     * @param relPath path relative to {@link #sourceRepoDir}, i.e.
     * img/mypic.jpg or docs/README.md
     * @param version The version the md page refers to.
     * @param relpaths a list of relative paths for the sidebar
     */
    void copyMdAsHtml(InputStream sourceMdStream, String relPath, final SemVersion version, List<String> relpaths) {

        checkNotNull(version);
        checkNotEmpty(relPath, "Invalid relative path!");
        checkNotEmpty(relpaths, "Invalid relative paths!");

        final String prependedPath = Josmans.prependedPath(relPath);

        File targetFile = Josmans.targetFile(pagesDir, relPath, version);

        if (targetFile.exists()) {
            throw new RuntimeException("Trying to write md file to target that already exists!! Target is "
                    + targetFile.getAbsolutePath());
        }

        String sourceMdString;
        try {
            StringWriter writer = new StringWriter();
            IOUtils.copy(sourceMdStream, writer, "UTF-8"); // todo fixed encoding...
            sourceMdString = writer.toString();
        } catch (Exception ex) {
            throw new RuntimeException(
                    "Couldn't read source md stream! Target path is " + targetFile.getAbsolutePath(), ex);
        }

        Josmans.checkNotMeaningful(sourceMdString, "Invalid source md file!");

        String filteredSourceMdString = sourceMdString.replaceAll("#\\{version}", version.toString())
                .replaceAll("#\\{majorMinorVersion}", Josmans.majorMinor(version))
                .replaceAll("#\\{repoRelease}", Josmans.repoRelease(repoOrganization, repoName, version))
                .replaceAll("jedoc", "josman"); // for legacy compat 

        String skeletonString;
        try {
            StringWriter writer = new StringWriter();
            InputStream stream = Josmans.findResourceStream("/skeleton.html");
            IOUtils.copy(stream, writer, "UTF-8");
            skeletonString = writer.toString();
        } catch (Exception ex) {
            throw new RuntimeException("Couldn't read skeleton file!", ex);
        }

        String skeletonStringFixedPaths;
        if (Josmans.isRootpath(relPath)) {
            skeletonStringFixedPaths = skeletonString;
        } else {
            // fix paths
            skeletonStringFixedPaths = skeletonString.replaceAll("src=\"js/", "src=\"../js/")
                    .replaceAll("src=\"img/", "src=\"../img/").replaceAll("href=\"css/", "href=\"../css/");

        }

        Jerry skeleton = Jerry.jerry(skeletonStringFixedPaths);
        skeleton.$("title").text(repoTitle);
        String contentFromMdHtml = pegDownProcessor.markdownToHtml(filteredSourceMdString);
        Jerry contentFromMd = Jerry.jerry(contentFromMdHtml);

        contentFromMd.$("a").each(new JerryFunction() {

            @Override
            public boolean onNode(Jerry arg0, int arg1) {
                String href = arg0.attr("href");
                if (href.startsWith(prependedPath + "src")) {
                    arg0.attr("href", href.replace(prependedPath + "src",
                            Josmans.repoRelease(repoOrganization, repoName, version) + "/src"));
                    return true;
                }
                if (href.endsWith(".md")) {
                    arg0.attr("href", Josmans.htmlizePath(href));
                    return true;
                }

                if (href.equals(prependedPath + "../../wiki")) {
                    arg0.attr("href", href.replace(prependedPath + "../../wiki",
                            Josmans.repoWiki(repoOrganization, repoName)));
                    return true;
                }

                if (href.equals(prependedPath + "../../issues")) {
                    arg0.attr("href", href.replace(prependedPath + "../../issues",
                            Josmans.repoIssues(repoOrganization, repoName)));
                    return true;
                }

                if (href.equals(prependedPath + "../../milestones")) {
                    arg0.attr("href", href.replace(prependedPath + "../../milestones",
                            Josmans.repoMilestones(repoOrganization, repoName)));
                    return true;
                }

                if (TodUtils.removeTrailingSlash(href).equals(DOCS_FOLDER)) {
                    arg0.attr("href", Josmans.majorMinor(version) + "/index.html");
                    return true;
                }

                if (href.startsWith(DOCS_FOLDER + "/")) {
                    arg0.attr("href", Josmans.majorMinor(version) + href.substring(DOCS_FOLDER.length()));
                    return true;
                }

                return true;
            }
        });

        contentFromMd.$("img").each(new JerryFunction() {

            @Override
            public boolean onNode(Jerry arg0, int arg1) {
                String src = arg0.attr("src");
                if (src.startsWith(DOCS_FOLDER + "/")) {
                    arg0.attr("src", Josmans.majorMinor(version) + src.substring(DOCS_FOLDER.length()));
                    return true;
                }
                return true;
            }
        });

        skeleton.$("#josman-internal-content").html(contentFromMd.html());

        skeleton.$("#josman-repo-link").html(repoTitle).attr("href", prependedPath + "index.html");

        File programLogo = programLogo(sourceDocsDir(), repoName);

        if (programLogo.exists()) {
            skeleton.$("#josman-program-logo").attr("src", prependedPath + "img/" + repoName + "-logo-200px.png");
            skeleton.$("#josman-program-logo-link").attr("href", prependedPath + "index.html");
        } else {
            skeleton.$("#josman-program-logo-link").css("display", "none");
        }

        skeleton.$("#josman-wiki").attr("href", Josmans.repoWiki(repoOrganization, repoName));
        skeleton.$("#josman-project").attr("href", Josmans.repoUrl(repoOrganization, repoName));

        skeleton.$("#josman-home").attr("href", prependedPath + "index.html");
        if (Josmans.isRootpath(relPath)) {
            skeleton.$("#josman-home").addClass("josman-tag-selected");
        }

        // cleaning example versions
        skeleton.$(".josman-version-tab-header").remove();

        List<RepositoryTag> tags = new ArrayList(
                Josmans.versionTagsToProcess(repoName, repoTags, ignoredVersions).values());
        Collections.reverse(tags);

        if (Josmans.isRootpath(relPath)) {
            skeleton.$("#josman-internal-sidebar").text("");
            skeleton.$("#josman-sidebar-managed-block").css("display", "none");
        } else {
            Jerry sidebar = makeSidebar(contentFromMdHtml, relPath, relpaths);
            skeleton.$("#josman-internal-sidebar").html(sidebar.htmlAll(true));
        }

        skeleton.$(".josman-to-strip").remove();

        if (snapshotMode) {

            if (tags.size() > 0) {
                SemVersion ver = Josmans.version(repoName, tags.get(0).getName());
                if (version.getMajor() >= ver.getMajor() && version.getMinor() >= ver.getMinor()) {
                    addVersionHeaderTag(skeleton, prependedPath, version, prependedPath.length() != 0);
                }

            } else {
                addVersionHeaderTag(skeleton, prependedPath, version, prependedPath.length() != 0);
            }

        } else {

            for (RepositoryTag tag : tags) {
                SemVersion ver = Josmans.version(repoName, tag.getName());
                addVersionHeaderTag(skeleton, prependedPath, ver,
                        !Josmans.isRootpath(relPath) && ver.equals(version));
            }

            Pattern p = Pattern.compile("todo", Pattern.CASE_INSENSITIVE);
            Matcher matcher = p.matcher(skeleton.html());
            if (matcher.find()) {
                //throw new RuntimeException("Found '" + matcher.group() + "' string in stream for " + targetFile.getAbsolutePath() + " (at position " + matcher.start() + ")");
                LOG.warning("Found '" + matcher.group() + "' string in stream for " + targetFile.getAbsolutePath());
            }
        }

        if (!targetFile.getParentFile().exists()) {
            if (!targetFile.getParentFile().mkdirs()) {
                throw new RuntimeException("Couldn't create target directories to host processed md file "
                        + targetFile.getAbsolutePath());
            }
        }

        try {

            FileUtils.write(targetFile, skeleton.html());
        } catch (Exception ex) {
            throw new RuntimeException("Couldn't write into " + targetFile.getAbsolutePath() + "!", ex);
        }

    }

    private static void addVersionHeaderTag(Jerry skeleton, String prependedPath, SemVersion version,
            boolean selected) {
        String verShortName = Josmans.majorMinor(version);
        String classSelected = selected ? "josman-tag-selected" : "";
        skeleton.$("#josman-usage").append("<a class='josman-version-tab-header " + classSelected + "' href='"
                + prependedPath + verShortName + "/index.html'>" + verShortName + "</a>");
    }

    private void buildIndex(SemVersion latestVersion) {
        try {
            File sourceMdFile = new File(sourceRepoDir, README_MD);
            copyMdAsHtml(new FileInputStream(sourceMdFile), README_MD, latestVersion, ImmutableList.of(README_MD));
        } catch (FileNotFoundException ex) {
            throw new RuntimeException("Error while building index!", ex);
        }
    }

    private File targetVersionDir(SemVersion semVersion) {
        checkNotNull(semVersion);
        return new File(pagesDir, "" + semVersion.getMajor() + "." + semVersion.getMinor());
    }

    /**
     * Returns the directory where docs about the latest version will end up.
     */
    private File targetLatestDocsDir() {
        return new File(pagesDir, "latest");
    }

    /**
     * Processes a directory 'docs' that holds documentation for a given version
     * of the software
     */
    private void processDocsDir(SemVersion version) {
        checkNotNull(version);

        if (!sourceDocsDir().exists()) {
            throw new RuntimeException("Can't find source dir!" + sourceDocsDir().getAbsolutePath());
        }

        File targetVersionDir = targetVersionDir(version);

        deleteOutputVersionDir(targetVersionDir, version.getMajor(), version.getMinor());

        List<String> relPaths = new ArrayList<String>();
        File[] files = sourceDocsDir().listFiles();
        //If this pathname does not denote a directory, then listFiles() returns null. 

        for (File file : files) {
            if (file.isFile() && file.getName().endsWith(".md")) {
                relPaths.add(DOCS_FOLDER + "/" + file.getName());
            }
        }

        DirWalker dirWalker = new DirWalker(sourceDocsDir(), targetVersionDir, this, version, relPaths);
        dirWalker.process();
        copyJavadoc(version);
    }

    /**
     * @param path the exact path. Careful: must be *exact* dir name (i.e.
     * 'docs' will work for docs/a.txt but 'doc' won't work)
     * @throws RuntimeException on error
     */
    private TreeWalk makeGitDocsWalk(RevTree tree, String path) {
        checkNotNull(tree);
        try {
            TreeWalk treeWalk = new TreeWalk(repo);
            treeWalk.addTree(tree);
            treeWalk.setRecursive(true);

            treeWalk.setFilter(PathFilter.create(path));
            return treeWalk;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Processes a directory 'docs' at tag repoName-version that holds
     * documentation for a given version of the software
     */
    private void processGitDocsDir(SemVersion version) {
        checkNotNull(version);

        checkNotNull(repo);

        String releaseTag = Josmans.releaseTag(repoName, version);

        try {
            ObjectId lastCommitId = repo.resolve(releaseTag);

            // a RevWalk allows to walk over commits based on some filtering that is defined
            DepthWalk.RevWalk revWalk = new DepthWalk.RevWalk(repo, DEPTH);
            RevCommit commit = revWalk.parseCommit(lastCommitId);
            RevTree tree = commit.getTree();

            TreeWalk relPathsWalk = makeGitDocsWalk(tree, DOCS_FOLDER);

            List<String> relpaths = new ArrayList();
            while (relPathsWalk.next()) {
                String pathString = relPathsWalk.getPathString();

                if (pathString.endsWith(".md")) {
                    FileMode fileMode = relPathsWalk.getFileMode(0);
                    LOG.log(Level.FINE, "Collecting {0}:  mode: {1}, type: {2}", new Object[] { pathString,
                            Josmans.gitFileModeToString(fileMode), fileMode.getObjectType() });
                    relpaths.add(pathString);
                }
            }

            String path;
            List<String> fixedRelpaths;

            if (relpaths.isEmpty()) {
                LOG.log(Level.WARNING, "COULDN''T FIND ANY FILE IN " + DOCS_FOLDER
                        + " for version {0}! TRYING TO USE README.md instead", version);
                path = README_MD;
            } else {
                path = DOCS_FOLDER;
            }

            TreeWalk treeWalk = makeGitDocsWalk(tree, path);
            while (treeWalk.next()) {

                String pathString = treeWalk.getPathString();

                FileMode fileMode = treeWalk.getFileMode(0);
                LOG.log(Level.FINE, "{0}:  mode: {1}, type: {2}", new Object[] { pathString,
                        Josmans.gitFileModeToString(fileMode), fileMode.getObjectType() });

                ObjectId objectId = treeWalk.getObjectId(0);
                ObjectLoader loader = repo.open(objectId);

                InputStream stream = loader.openStream();

                if (relpaths.isEmpty()) {
                    copyStream(stream, DOCS_FOLDER + "/" + pathString, version,
                            ImmutableList.of(DOCS_FOLDER + "/" + path));
                } else {
                    copyStream(stream, pathString, version, relpaths);
                }

            }

            copyJavadoc(version);
        }

        catch (Exception ex) {
            throw new RuntimeException("Error while extracting docs from git local repo at commit " + releaseTag,
                    ex);
        }

        // ------------------------------------------------------
        // File sourceMdFile = new File(wikiDir, "userdoc\\" + ver.getMajor() + ver.getMinor() + "\\Usage.md");
        // File outputFile = new File(pagesDir, version + "\\usage.html");
        // buildMd(sourceMdFile, outputFile, "../");            
        /*
         if (!sourceDocsDir().exists()) {
         throw new RuntimeException("Can't find source dir!" + sourceDocsDir().getAbsolutePath());
         }
            
            
         File targetVersionDir = targetVersionDir(version);
            
         deleteOutputVersionDir(targetVersionDir, version.getMajor(), version.getMinor());
            
         DirWalker dirWalker = new DirWalker(
         sourceDocsDir(),
         targetVersionDir,
         this,
         version
         );
         dirWalker.process();
            
         copyJavadoc(version);
         */
    }

    /**
     * Copies target version to 'latest' directory, cleaning it before the copy
     *
     * @param version
     */
    private void createLatestDocsDirectory(SemVersion version) {

        File targetLatestDocsDir = targetLatestDocsDir();
        LOG.log(Level.INFO, "Creating latest docs directory {0}", targetLatestDocsDir.getAbsolutePath());

        if (!targetLatestDocsDir.getAbsolutePath().endsWith("latest")) {
            throw new RuntimeException("Trying to delete a latest docs dir which doesn't end with 'latest'!");
        }
        try {
            LOG.log(Level.INFO, "Deleting directory {0}  ...", targetLatestDocsDir.getAbsolutePath());
            FileUtils.deleteDirectory(targetLatestDocsDir);
            LOG.log(Level.INFO, "Done deleting directory.");
            LOG.log(Level.INFO, "Copying files from directory {0} to {1}  ...", new Object[] {
                    targetVersionDir(version).getAbsolutePath(), targetLatestDocsDir.getAbsolutePath() });
            FileUtils.copyDirectory(targetVersionDir(version), targetLatestDocsDir);
            LOG.log(Level.INFO, "Done copying directory.");
        } catch (Throwable tr) {
            throw new RuntimeException("Error while creating latest docs directory ", tr);
        }

    }

    /**
     * Returns true if the website generator will only process the current
     * branch snapshot. Otherwise all released versions will be processed.
     *
     */
    public boolean isSnapshotMode() {
        return snapshotMode;
    }

    public void generateSite() {

        LOG.log(Level.INFO, "Fetching {0}/{1} tags.", new Object[] { repoOrganization, repoName });
        repoTags = Josmans.fetchTags(repoOrganization, repoName);
        MavenXpp3Reader reader = new MavenXpp3Reader();

        try {
            pom = reader.read(new FileInputStream(new File(sourceRepoDir, "pom.xml")));
        } catch (Exception ex) {
            throw new RuntimeException("Error while reading pom!", ex);
        }

        try {
            File repoFile = new File(sourceRepoDir, ".git");

            FileRepositoryBuilder builder = new FileRepositoryBuilder();
            repo = builder.setGitDir(repoFile).readEnvironment() // scan environment GIT_* variables
                    .build();
        } catch (Exception ex) {
            throw new RuntimeException("Error while reading local git repo!", ex);
        }

        LOG.log(Level.INFO, "Cleaning target: {0}  ....", pagesDir.getAbsolutePath());
        if (!pagesDir.getAbsolutePath().endsWith("site")) {
            throw new RuntimeException("target directory does not end with 'site' !");
        }
        try {
            FileUtils.deleteDirectory(pagesDir);
            LOG.info("Done deleting directory");
        } catch (IOException ex) {
            throw new RuntimeException("Error while deleting directory " + pagesDir.getAbsolutePath(), ex);
        }

        SemVersion snapshotVersion = SemVersion.of(pom.getVersion()).withPreReleaseVersion("");

        if (snapshotMode) {
            LOG.log(Level.INFO, "Processing local version");

            buildIndex(snapshotVersion);
            processDocsDir(snapshotVersion);
            createLatestDocsDirectory(snapshotVersion);

        } else {
            if (repoTags.isEmpty()) {
                throw new NotFoundException("There are no tags at all in the repository!!");
            }
            SemVersion latestPublishedVersion = Josmans.latestVersion(repoName, repoTags);
            LOG.log(Level.INFO, "Processing published version");
            buildIndex(latestPublishedVersion);
            String curBranch = Josmans.readRepoCurrentBranch(sourceRepoDir);

            SortedMap<String, RepositoryTag> filteredTags = Josmans.versionTagsToProcess(repoName, repoTags,
                    ignoredVersions);

            for (RepositoryTag tag : filteredTags.values()) {
                LOG.log(Level.INFO, "Processing release tag {0}", tag.getName());
                processGitDocsDir(Josmans.version(repoName, tag.getName()));
            }

        }

        Josmans.copyDirFromResource(Josmans.class, "/website-template", pagesDir);

        try {

            File targetImgDir = new File(pagesDir, "img");

            File programLogo = programLogo(sourceDocsDir(), repoName);

            if (programLogo.exists()) {
                LOG.log(Level.INFO, "Found program logo: {0}", programLogo.getAbsolutePath());
                LOG.log(Level.INFO, "      copying it into dir {0}", targetImgDir.getAbsolutePath());

                FileUtils.copyFile(programLogo, new File(targetImgDir, programLogoName(repoName)));
            }

            FileUtils.copyFile(new File(sourceRepoDir, "LICENSE.txt"), new File(pagesDir, "LICENSE.txt"));

        } catch (Exception ex) {
            throw new RuntimeException("Error while copying files!", ex);
        }

        LOG.log(Level.INFO, "\n\nSite is now browsable at {0}\n\n", pagesDir.getAbsolutePath());
    }

    /**
     * Returns a Jerry object repereenting the non-managed sidebar for a given
     * version page
     *
     * @param contentFromMdHtml the content of page we're making the sidebar for
     * @param relpaths a list of relpaths of pages related to the version we're
     * making the sidebar for
     * @param currentRelPath the relpath of the page we're making the sidebar
     * for
     */
    private Jerry makeSidebar(String contentFromMdHtml, String currentRelPath, List<String> relpaths) {
        checkNotNull(contentFromMdHtml);
        checkNotEmpty(currentRelPath, "Invalid current rel path!");
        checkNotEmpty(relpaths, "Invalid list of relpaths!");

        Jerry html = Jerry.jerry(contentFromMdHtml);

        Jerry allLinksContainer = Jerry.jerry("<ul>").$("ul").addClass("josman-tree");

        List<String> orderedRelpaths = Josmans.orderRelpaths(relpaths);

        for (String relpath : orderedRelpaths) {
            Jerry pageItemContainer = Jerry.jerry("<li>").$("li");

            Jerry pageTitle = Jerry.jerry("<div>").$("div").addClass("josman-sidebar-page-title");

            Jerry pageLinksContainer = Jerry.jerry("<ul>").$("ul").addClass("josman-tree");

            if (relpath.equals(currentRelPath)) {
                pageTitle.text(Josmans.targetName(relpath));
                pageTitle.addClass("josman-sidebar-selected");

                for (Jerry sourceHeaderLink : html.$("h3 a")) {
                    // <a href="#header1">Header 1</a><br/>

                    /*<ul class="josman-tree">
                     <li>
                     <div class="josman-sidebar-page-title">Usage</div>
                     <ul class="josman-tree">
                     <li><a href="#header1">Maven</a>   */
                    Jerry linkContainer = Jerry.jerry("<div>").$("div");

                    Jerry link = Jerry.jerry("<a>").$("a")
                            .attr("href", sourceHeaderLink.first().first().attr("href"))
                            .text(sourceHeaderLink.first().text());
                    linkContainer.append(link.htmlAll(true));

                    pageLinksContainer.append(linkContainer.htmlAll(true));

                    // ret += "<div> <a href='" + sourceHeaderLink.first().first().attr("href") + "'>" + sourceHeaderLink.first().text() + "</a></div> \n";
                    /*Jerry.jerry("<a>")
                     .attr("href","#" + sourceHeaderLink.attr("id"))
                     .text(sourceHeaderLink.text()); */
                }
            } else {
                pageTitle.append(Jerry.jerry("<a>").$("a")
                        .attr("href", Josmans.htmlizePath(relpath.substring(DOCS_FOLDER.length() + 1)))
                        .text(Josmans.targetName(relpath)).htmlAll(true));
            }

            pageItemContainer.append(pageTitle.htmlAll(true));
            pageItemContainer.append(pageLinksContainer.htmlAll(true));

            allLinksContainer.append(pageItemContainer.htmlAll(true));
        }

        return allLinksContainer;
    }

    /**
     * Copies javadoc into target website according to the artifact version.
     */
    private void copyJavadoc(SemVersion version) {
        File targetJavadoc = targetJavadocDir(version);
        if (targetJavadoc.exists() && (targetJavadoc.isFile() || targetJavadoc.length() > 0)) {
            throw new RuntimeException(
                    "Target directory for Javadoc already exists!!! " + targetJavadoc.getAbsolutePath());
        }
        if (snapshotMode) {
            File sourceJavadoc = sourceJavadocDir(version);
            if (sourceJavadoc.exists()) {

                try {
                    LOG.log(Level.INFO, "Now copying Javadoc from {0} to {1} ...",
                            new Object[] { sourceJavadoc.getAbsolutePath(), targetJavadoc.getAbsolutePath() });
                    FileUtils.copyDirectory(sourceJavadoc, targetJavadoc);
                    LOG.info("Done copying javadoc.");
                } catch (Exception ex) {
                    throw new RuntimeException("Error while copying Javadoc from " + sourceJavadoc.getAbsolutePath()
                            + " to " + targetJavadoc.getAbsolutePath(), ex);
                }
            } else {
                LOG.log(Level.INFO, "Couldn''t find javadoc, skipping it. Looked in {0}",
                        sourceJavadoc.getAbsolutePath());
            }
        } else {
            File jardocs;
            try {
                jardocs = Josmans.fetchJavadoc(pom.getGroupId(), pom.getArtifactId(), version);
            } catch (Exception ex) {
                String sep = File.separator;
                String localJarPath = sourceRepoDir.getAbsolutePath() + sep + "target" + sep + "checkout" + sep
                        + "target" + sep + Josmans.javadocJarName(repoName, version);
                LOG.log(Level.WARNING,
                        "Error while fetching javadoc from Maven Central, trying to locate it at " + localJarPath,
                        ex);
                jardocs = new File(localJarPath);
                if (!jardocs.exists()) {
                    throw new RuntimeException("Couldn't find any jar for javadoc!");
                }
            }
            Josmans.copyDirFromJar(jardocs, targetJavadocDir(version), "");
        }

    }

    public String getRepoName() {
        return repoName;
    }

    public String getRepoOrganization() {
        return repoOrganization;
    }

    public ImmutableList<SemVersion> getIgnoredVersions() {
        return ignoredVersions;
    }

}