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

com.redhat.ceylon.tools.bashcompletion.CeylonBashCompletionTool 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.tools.bashcompletion;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;

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.OptionArgument;
import com.redhat.ceylon.common.tool.OptionModel;
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.OptionModel.ArgumentType;
import com.redhat.ceylon.common.tools.CeylonTool;

@Hidden
@Summary( 
value="A tool which provides completion suggestions for the Bash shell.")
@Description("The `` are the elements of the `${COMP_WORDS}` bash array variable.\n" +
		"\n" +
		"The tool inspects the `` and writes its completions to standard output." +
		"Currently the tool can complete\n" +
		"\n" +
		"* tool names (except tools names which are arguments to another tool),\n" +
		"* long option names,\n" +
		"* long option values **if** the setter type is a `java.lang.File` or a subclass" +
		"  of `java.lang.Enum`.")
public class CeylonBashCompletionTool implements Tool {

    public static class CompletionResults {
        
        private List results = new ArrayList();
        private String prefix = "";
        private String partial = "";
        
        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }
        
        public void setPartial(String partial) {
            this.partial = partial;
        }
        
        public boolean addResult(String completion) {
            if (completion.startsWith(partial)) {
                results.add(completion);
                return true;
            }
            return false;
        }
        
        public void emitCompletions() {
            boolean appendSpace = results.size() == 1;
            for (String result : results) {
                String completion;
                if (prefix.isEmpty()) {
                    completion = escape(result);
                } else {
                    completion = escape(prefix + result.substring(partial.length()));
                }
                if (appendSpace) {
                    // If there's only one result, append a space
                    completion = completion + " ";
                }
                System.out.println(completion);
            }
            System.out.flush();
        }
        
        private String escape(String completion) {
            return completion.replace("\n", "\\\n");
        }
    }
    
    private int cword = -1;
    private List arguments;
    private ToolLoader toolLoader;
    
    @OptionArgument
    @Description("The index in the `` of the argument being " +
    		"completed, i.e. The value of `${COMP_CWORD}`.")
    public void setCword(int word) {
        this.cword = word;
    }
    
    @Argument(argumentName="arguments", multiplicity="*")
    public void setArguments(List arguments) {
        this.arguments = arguments;
    }
    
    public final void setToolLoader(ToolLoader toolLoader) {
        this.toolLoader = toolLoader;
    }

    @Override
    public void initialize(CeylonTool mainTool) {
    }
    
    @Override
    public void run() throws Exception {
        arguments.remove(0);// we don't care about arg0
        cword--;
        final CompletionResults results;
        if (cword == 0) {
            // We're completing the name of the tool to run
            results = completeToolNames(arguments.isEmpty() ? "" : arguments.get(cword));
        } else if (cword < arguments.size()) {
            String argument = arguments.get(cword);
            CeylonTool main = new CeylonTool();
            main.setArgs(arguments);
            main.setToolLoader(toolLoader);
            ToolModel tool = main.getToolModel();
            if (!afterEoo()) {
                if (argument.startsWith("--")) {
                    if (argument.contains("=")) {
                        results = completeLongOptionArgument(tool, argument);
                    } else {
                        results = completeLongOption(tool, argument);
                    }
                } else if (argument.startsWith("-")) {
                    /*TODO for (OptionModel option : tool.getOptions()) {
                        if (argument.charAt(argument.length()-1) == option.getShortName()) {
                            complete
                        }
                    }*/
                    results = new CompletionResults();
                } else {
                    // TODO it's argument completion unless the previous argument was a 
                    // non-pure short option
                    results = new CompletionResults();
                }
            } else {
                // TODO else it must be argument completion
                results = new CompletionResults();
            }
        } else {
            // TODO we don't know what we're completing. 
            // First assume it's an argument...
            // ... but if the tool doesn't have any arguments (or all the 
            // arguments are already specified) then assume it's an option
            results = new CompletionResults();
        }
        results.emitCompletions();
    }

    private CompletionResults completeLongOptionArgument(ToolModel tool, final String argument) {
        int index = argument.indexOf('=');
        String optionName = argument.substring(2, argument.charAt(index-1) == '\\' ? index-1 : index);
        String partialValue = argument.substring(index+1);
        OptionModel option = tool.getOption(optionName);
        Class type = option.getArgument().getType();
        /*
         * In general,
         * 1. instantiate the tool, binding all arguments except
         *    the one being completed. do not invoke @PostConstruct
         * 2. If the option's setter has @CompletedBy(method=methodname) 
         *    call methodname(partial);
         * 3. If the option's setter has @CompletedBy(Completer)
         *    then instantiate the given completer and  
         *    call Completer.complete(tool, partial)
         * 4. If the setter type is annotated with @CompletedBy
         *    then instantiate the given completer and  
         *    call Completer.complete(tool, partial). 
         * 5. Revert to some build in completions.
         */
        if (File.class.isAssignableFrom(type)) {
            return completeFilename(argument, partialValue, true, null);
        } else if (Enum.class.isAssignableFrom(type)) {
            return completeEnum(argument, (Class)type, partialValue);
        }
        
        return new CompletionResults();
    }

    private > CompletionResults completeEnum(String argumentPrefix, Class type, String partialValue) {
        try {
            CompletionResults result = new CompletionResults();
            result.setPartial(partialValue);
            result.setPrefix(argumentPrefix);
            Method method = type.getMethod("values", (Class[])null);
            E[] values = (E[])method.invoke(null);
            for (E value : values) {
                String enumElementName = value.toString();
                result.addResult(enumElementName);
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    private CompletionResults completeFilename(String argumentPrefix, String partialValue, boolean wantFiles, 
            final FileFilter fileFilter) {
        File file;
        final String partial;
        if (partialValue.isEmpty()) {
            file = new File(".").getAbsoluteFile();
            partial = "";
        } else {
            file = new File(partialValue).getAbsoluteFile();
            partial = file.getName();
        }
        
        File[] files = file.getParentFile().listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                String name = pathname.getName();
                if (pathname.isFile() && fileFilter != null) {
                    return name.startsWith(partial) && fileFilter.accept(pathname);
                }
                return name.startsWith(partial);
            }
        });
        CompletionResults results = new CompletionResults();
        results.setPrefix(argumentPrefix);
        results.setPartial(partialValue);
        if (files != null) {
            for (File f : files) {
                if (f.isDirectory() && wantFiles) {
                    results.addResult(f.getName()+"/");
                } else {
                    results.addResult(f.getName());
                }
            }
        }
        return results;
    }

    private CompletionResults completeLongOption(ToolModel tool, String argument) {
        Comparator> comparator = new Comparator>() {
            @Override
            public int compare(OptionModel o1, OptionModel o2) {
                return o1.getLongName().compareTo(o2.getLongName());
            }
        };
        CompletionResults results = new CompletionResults();
        results.setPartial(argument);
        TreeSet> sorted = new TreeSet>(comparator);
        sorted.addAll(tool.getOptions());
        for (OptionModel option : sorted) {
            results.addResult("--" + option.getLongName() + (option.getArgumentType() == ArgumentType.NOT_ALLOWED ? "" : "\\="));
        }
        return results;
    }

    private boolean afterEoo() {
        boolean eoo = false;
        for (int ii = 0; ii < cword; ii++) {
            if (arguments.get(ii).equals("--")) {
                eoo = true;
                break;
            }
        }
        return eoo;
    }

    private CompletionResults completeToolNames(String partial) {
        if(partial.indexOf(',') != -1){
            // complete the last tool after the comma, even if it's empty
            partial = partial.substring(partial.lastIndexOf(',')+1);
        }
        CompletionResults results = new CompletionResults();
        results.setPartial(partial);
        for (String toolName : toolLoader.getToolNames()) {
            if (toolLoader.loadToolModel(toolName).isPorcelain()) {
                results.addResult(toolName);
            }
        }
        return results;
    }
    
    

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy