<commit_before>/*
 * 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.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 `<tool>` is given, displays help about that ceylon tool on the standard output.\n\n" +
"If no `<tool>` 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()){
            openHelpInBrowser();
            return;
        }
        
        docBuilder.setIncludeHidden(includeHidden);
        Doc doc;
        if (tool != null) {
            doc = docBuilder.buildDoc(tool);
        } else {
            final ToolModel<CeylonTool> root = toolLoader.loadToolModel("");
            doc = docBuilder.buildDoc(root, true);
        }
        Process pagerProcess = null;
        if(wantsPager){
            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<String>(Arrays.asList(options.trim().split("\\s*,\\s*"))));<commit_msg>Fix NPE when no tool is set (eg. user types `ceylon --help`). On Windows now fall back to the console if no doc files were found (ceylon/ceylon-compiler#1506)<commit_after>/*
 * 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.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 `<tool>` is given, displays help about that ceylon tool on the standard output.\n\n" +
"If no `<tool>` 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<CeylonTool> 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<String>(Arrays.asList(options.trim().split("\\s*,\\s*"))));
{'fraction_non_alphanumeric': 0.09295883031935359, 'fraction_numerical': 0.0021546748749519044, 'mean_word_length': 2.5353645266594125, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '675833', 'n_tokens_mistral': 3663, 'n_tokens_neox': 3552, 'n_words': 1113}