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

com.redhat.ceylon.common.tools.help.CeylonHelpTool 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.common.tools.help;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.ProcessBuilder.Redirect;
import java.util.Arrays;
import java.util.HashSet;

import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.OSUtil;
import com.redhat.ceylon.common.config.CeylonConfig;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Hidden;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.Tool;
import com.redhat.ceylon.common.tool.ToolLoader;
import com.redhat.ceylon.common.tool.ToolModel;
import com.redhat.ceylon.common.tool.ToolUsageError;
import com.redhat.ceylon.common.tool.WordWrap;
import com.redhat.ceylon.common.tools.CeylonTool;
import com.redhat.ceylon.common.tools.help.model.Doc;
import com.redhat.ceylon.common.tools.help.model.Visitor;

/**
 * A plugin which provides help about other plugins 
 * @author tom
 */
@Summary("Displays help information about other Ceylon tools")
@Description(
"If a `` is given, displays help about that ceylon tool on the standard output.\n\n" +
"If no `` is given, displays the synopsis of the top level `ceylon` command. "
)
@RemainingSections(
"## SEE ALSO\n\n" +
"* `ceylon doc-tool` for generating documentation about ceylon tools\n"
)
public class CeylonHelpTool implements Tool {

    private Appendable out;
    private boolean includeHidden;
    private ToolLoader toolLoader;
    private DocBuilder docBuilder;
    private ToolModel tool;
    private boolean synopsis = false;
    private String options = null;
    private boolean wantsPager;
    
    public final void setToolLoader(ToolLoader toolLoader) {
        this.toolLoader = toolLoader;
        this.docBuilder = new DocBuilder(toolLoader);
    }
    
    @Option
    public void setIncludeHidden(boolean includeHidden) {
        this.includeHidden = includeHidden;
    }
    
    @Hidden
    @Option
    @Description("Used to generate a synopsis when another tool was invoked incorrectly")
    public void setSynopsis(boolean synopsis) {
        this.synopsis = synopsis;
    }
    
    @Hidden
    @Option
    @OptionArgument
    @Description("Used to generate doc on a given option (or options)")
    public void setOptions(String options) {
        this.options = options;
    }
    
    @Argument(argumentName="tool", multiplicity="?")
    public void setTool(ToolModel tool) {
        this.tool = tool;
    }
    
    public void setOut(Appendable out) {
        this.out = out;
    }

    @Override
    public void initialize(CeylonTool mainTool) {
        Boolean wantsPager = mainTool.getWantsPager();
        // tool-specific option
        if(wantsPager == null){
            String option = CeylonConfig.get().getOption("help.pager");
            if(option != null){
                if(CeylonConfig.isFalsish(option))
                    wantsPager = Boolean.FALSE;
                else // any other value is either true or a pager command
                    wantsPager = Boolean.TRUE;
            }
        }
        // general option
        if(wantsPager == null){
            String option = CeylonConfig.get().getOption("defaults.pager");
            if(option != null){
                if(CeylonConfig.isFalsish(option))
                    wantsPager = Boolean.FALSE;
                else // any other value is either true or a pager command
                    wantsPager = Boolean.TRUE;
            }
        }
        // allow pager if it's unspecified (default) or if it's not explicitly disabled
        if((wantsPager == null 
                || wantsPager.booleanValue())
                // and if stdout is on a tty
                // on this we differ with git which only tests isatty(stdout) and System.console() will return null
                // if stdout is on a tty but stdin on a pipe, but I've to say that I can't understand what a pager
                // is good for if stdin is not on a tty...
                && System.console() != null){
            this.wantsPager = true;
        }
    }
    
    @Override
    public void run() {
        if(wantsPager && OSUtil.isWindows() && tool != null){
            if (openHelpInBrowser()){
                return;
            }
        }
        
        docBuilder.setIncludeHidden(includeHidden);
        Doc doc;
        if (tool != null) {
            doc = docBuilder.buildDoc(tool);
        } else {
            final ToolModel root = toolLoader.loadToolModel("");
            doc = docBuilder.buildDoc(root, true);
        }
        Process pagerProcess = null;
        if(wantsPager && !OSUtil.isWindows()){
            pagerProcess = startPager();
        }
        OutputStream pagerPipe = null;
        if(pagerProcess != null)
            pagerPipe = pagerProcess.getOutputStream();
        try{
            final WordWrap wrap = getWrap(pagerPipe);
            Visitor plain = new PlainVisitor(wrap);
            if (synopsis) {
                plain = new SynopsisOnlyVisitor(plain);
            } else if (options != null) {
                plain = new OptionsOnlyVisitor(plain, 
                        new HashSet(Arrays.asList(options.trim().split("\\s*,\\s*"))));
            }
            doc.accept(plain);
            wrap.flush();
        }finally{
            // shutdown pager
            if(pagerPipe != null){
                try {
                    pagerPipe.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                // wait for pager to be done, there's no point doing anything else meanwhile
                try {
                    int errorCode = pagerProcess.waitFor();
                    if(errorCode != 0){
                        throw new ToolUsageError("Pager process returned an error exit code: "+errorCode+". Try fixing your $CEYLON_PAGER or $PAGER environment variable or invoke with the --no-pager command-line option.");
                    }
                } catch (InterruptedException e) {
                    throw new ToolUsageError("Pager process interrupted. Try fixing your $CEYLON_PAGER or $PAGER environment variable or invoke with the --no-pager command-line option.");
                }
            }
        }
    }

    /** this is only for windows **/
    private boolean openHelpInBrowser() {
        String ceylonHome = System.getProperty(Constants.PROP_CEYLON_HOME_DIR);
        if(ceylonHome == null || ceylonHome.isEmpty())
            return false;
        String toolDoc = "ceylon";
        if(tool.getName() != null && !tool.getName().isEmpty())
            toolDoc += "-"+tool.getName();
        toolDoc += ".html";
        File docHome = new File(ceylonHome, "doc/en/toolset/");
        File toolDocFile = new File(docHome, toolDoc);
        if(toolDocFile.exists() && toolDocFile.isFile()){
            try {
                Desktop.getDesktop().browse(toolDocFile.toURI());
            } catch (Exception e) {
                throw new ToolUsageError("Could not open browser for uri '"+toolDocFile.toURI()+"'. Please invoke with the --no-pager command-line option.");
            }
            return true;
        }else
            return false;
    }

    private Process startPager() {
        // find out the pager process, same process as in git
        // first try ceylon-specific pager env var
        String pager = System.getenv("CEYLON_PAGER");
        if(pager == null){
            // first try tool-specific option
            String option = CeylonConfig.get().getOption("help.pager");
            // if it's neither true nor false it's a pager program
            if(option != null 
                    && !CeylonConfig.isTrueish(option)
                    && !CeylonConfig.isFalsish(option))
                pager = option;
        }
        if(pager == null){
            // then try general option
            String option = CeylonConfig.get().getOption("defaults.pager");
            // if it's neither true nor false it's a pager program
            if(option != null 
                    && !CeylonConfig.isTrueish(option)
                    && !CeylonConfig.isFalsish(option))
                pager = option;
        }
        // then try general pager env var
        if(pager == null)
            pager = System.getenv("PAGER");
        // default to less
        if(pager == null)
            pager = "less";
        // make sure we don't page for cat
        if(!pager.equals("cat")){
            // start the pager process
            ProcessBuilder pb = new ProcessBuilder(pager);
            pb.redirectError(Redirect.INHERIT);
            pb.redirectOutput(Redirect.INHERIT);
            try {
                return pb.start();
            } catch (IOException e) {
                throw new ToolUsageError("Could not start pager process '"+pager+"'. Try fixing your $CEYLON_PAGER or $PAGER environment variable or invoke with the --no-pager command-line option.", e);
            }
        }
        return null;
    }

    private WordWrap getWrap(OutputStream pagerPipe) {
        Appendable destination = null;
        if(pagerPipe != null)
            destination = new PrintStream(pagerPipe);
        if(destination == null)
            destination = out;
        if(destination == null)
            destination = synopsis || options != null ? System.err : System.out;
        return new WordWrap(destination);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy