package org.yamcs.cli; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.api.YamcsConnectionProperties; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterDescription; import com.beust.jcommander.ParameterException; import com.beust.jcommander.internal.Console; /** * This represents a command together with its options and subcommands * * yamcs <options> subcmd <options> subcmd <options>... * */ public abstract class Command { static protected Console console = JCommander.getConsole(); protected JCommander jc = new JCommander(this); protected Map<String, Command> subCommands = new HashMap<>(); protected Command selectedCommand; final String name; final Command parent; Logger log = LoggerFactory.getLogger(this.getClass()); @Parameter(names="-h", description="help", help = true) private boolean help; private boolean ycpRequired = false; private boolean instanceRequired = false; public void setYcpRequired(boolean requiredYcp, boolean requireInstance) { this.ycpRequired = requiredYcp; this.instanceRequired = requireInstance; } public Command(String name, Command parent) { this.name = name; this.parent = parent; jc.setProgramName(getFullCommandName()); } protected void addSubCommand(Command cmd) { subCommands.put(cmd.name, cmd); jc.addCommand(cmd.name, cmd); } protected YamcsConnectionProperties getYamcsConnectionProperties() { Command c = this; while(c!=null) { if(c instanceof YamcsCli) { return ((YamcsCli) c).ycp; } c = c.parent; } return null; } public void parse(String[] args) { int k = 0; try { if(subCommands.isEmpty()) { jc.parse(args); } else { while(k<args.length) { if(args[k].startsWith("-")) { k+=getArity(args[k]); } else { break; } k++; } jc.parse(Arrays.copyOf(args, k)); } if(help) { console.println(getUsage()); System.exit(0); } } catch (ParameterException e) { console.println(e.getMessage()); console.println(getUsage()); System.exit(1); } if(subCommands.isEmpty()) { return; } if(k==args.length) { JCommander.getConsole().println(getUsage()); System.exit(1); } String subcmdName = args[k]; selectedCommand = subCommands.get(subcmdName); if(selectedCommand==null) { String fullcmd = getFullCommandName(); StringBuilder sb = new StringBuilder(); sb.append(fullcmd).append(": '").append(subcmdName) .append("'").append(" is not a valid command name. See '") .append(fullcmd) .append(" -h'"); JCommander.getConsole().println(sb.toString()); System.exit(1); } selectedCommand.parse(Arrays.copyOfRange(args, k+1, args.length)); } int getArity(String arg) { for(ParameterDescription pd: jc.getParameters()) { if(Arrays.asList(pd.getParameter().names()).contains(arg)) { return getArity(pd); } } throw new ParameterException("Unkown option '"+arg+"'"); } int getArity(ParameterDescription pd) { Class<?> fieldType = pd.getParameterized().getType(); if ((fieldType == boolean.class || fieldType == Boolean.class)) { return 0; } return pd.getParameter().arity()==-1?1:pd.getParameter().arity(); } String getFullCommandName() { List<Command> a = new ArrayList<Command>(); Command c = this; while(c!=null) { a.add(c); c = c.parent; } StringBuilder sb = new StringBuilder(); for(int i=a.size()-1;;i--) { sb.append(a.get(i).getName()); if(i==0) { break; } sb.append(" "); } return sb.toString(); } public String getName() { return name; } void execute() throws Exception { if(selectedCommand==null) { throw new IllegalStateException("Please implement the execute method in "+this); } else { selectedCommand.execute(); } } void validate() throws ParameterException { if(ycpRequired) { YamcsConnectionProperties ycp = getYamcsConnectionProperties(); if(ycp==null) { throw new ParameterException("This command requires a connection to a live yamcs. Please use the 'yamcs -y' option"); } if(instanceRequired && ycp.getInstance()==null) { throw new ParameterException("This command requires the yamcs instnace specified in the yamcs url. Please use the 'yamcs -y http://host:port/instance' option"); } } if(selectedCommand!=null) { selectedCommand.validate(); } } public String getUsage() { StringBuilder out = new StringBuilder(); out.append("usage: "+getFullCommandName()).append(" "); List<ParameterDescription> sorted = jc.getParameters(); Collections.sort(sorted, parameterDescriptionComparator); if(!sorted.isEmpty()) { out.append("[<options>]"); } if(!subCommands.isEmpty()) { out.append(" <command> [<command options>]"); } if(jc.getMainParameter()!=null) { out.append(" "); out.append(jc.getMainParameterDescription()); } out.append("\n"); if (sorted.size() > 0) { int maxLength = 3+sorted.stream().map(pd -> pd.getNames().length()).max(Integer::max).get(); out.append("Options:\n"); for (ParameterDescription pd : sorted) { out.append(String.format(" %-"+maxLength+"s %s\n",pd.getNames(), pd.getDescription())); } } if(!subCommands.isEmpty()) { out.append("Commands:\n"); int maxLength = 2+subCommands.values().stream().map(c -> c.getName().length()).max(Integer::max).get(); for(Command c: subCommands.values()) { out.append(String.format(" %-"+maxLength+"s %s\n",c.getName(), jc.getCommandDescription(c.getName()))); } } return out.toString(); } private Comparator<? super ParameterDescription> parameterDescriptionComparator = new Comparator<ParameterDescription>() { @Override public int compare(ParameterDescription p0, ParameterDescription p1) { return p0.getLongestName().compareTo(p1.getLongestName()); } }; }