CmdLineParser.java

  1. /*
  2.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.pgm.opt;

  11. import java.io.IOException;
  12. import java.io.Writer;
  13. import java.lang.reflect.Field;
  14. import java.util.ArrayList;
  15. import java.util.Iterator;
  16. import java.util.List;
  17. import java.util.ResourceBundle;

  18. import org.eclipse.jgit.lib.ObjectId;
  19. import org.eclipse.jgit.lib.Repository;
  20. import org.eclipse.jgit.pgm.Die;
  21. import org.eclipse.jgit.pgm.TextBuiltin;
  22. import org.eclipse.jgit.pgm.internal.CLIText;
  23. import org.eclipse.jgit.revwalk.RevCommit;
  24. import org.eclipse.jgit.revwalk.RevTree;
  25. import org.eclipse.jgit.revwalk.RevWalk;
  26. import org.eclipse.jgit.transport.RefSpec;
  27. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  28. import org.kohsuke.args4j.CmdLineException;
  29. import org.kohsuke.args4j.IllegalAnnotationError;
  30. import org.kohsuke.args4j.NamedOptionDef;
  31. import org.kohsuke.args4j.OptionDef;
  32. import org.kohsuke.args4j.OptionHandlerRegistry;
  33. import org.kohsuke.args4j.spi.OptionHandler;
  34. import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
  35. import org.kohsuke.args4j.spi.Setter;

  36. /**
  37.  * Extended command line parser which handles --foo=value arguments.
  38.  * <p>
  39.  * The args4j package does not natively handle --foo=value and instead prefers
  40.  * to see --foo value on the command line. Many users are used to the GNU style
  41.  * --foo=value long option, so we convert from the GNU style format to the
  42.  * args4j style format prior to invoking args4j for parsing.
  43.  */
  44. public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser {
  45.     static {
  46.         OptionHandlerRegistry registry = OptionHandlerRegistry.getRegistry();
  47.         registry.registerHandler(AbstractTreeIterator.class,
  48.                 AbstractTreeIteratorHandler.class);
  49.         registry.registerHandler(ObjectId.class, ObjectIdHandler.class);
  50.         registry.registerHandler(RefSpec.class, RefSpecHandler.class);
  51.         registry.registerHandler(RevCommit.class, RevCommitHandler.class);
  52.         registry.registerHandler(RevTree.class, RevTreeHandler.class);
  53.         registry.registerHandler(List.class, OptionWithValuesListHandler.class);
  54.     }

  55.     private final Repository db;

  56.     private RevWalk walk;

  57.     private boolean seenHelp;

  58.     private TextBuiltin cmd;

  59.     /**
  60.      * Creates a new command line owner that parses arguments/options and set
  61.      * them into the given object.
  62.      *
  63.      * @param bean
  64.      *            instance of a class annotated by
  65.      *            {@link org.kohsuke.args4j.Option} and
  66.      *            {@link org.kohsuke.args4j.Argument}. this object will receive
  67.      *            values.
  68.      * @throws IllegalAnnotationError
  69.      *             if the option bean class is using args4j annotations
  70.      *             incorrectly.
  71.      */
  72.     public CmdLineParser(Object bean) {
  73.         this(bean, null);
  74.     }

  75.     /**
  76.      * Creates a new command line owner that parses arguments/options and set
  77.      * them into the given object.
  78.      *
  79.      * @param bean
  80.      *            instance of a class annotated by
  81.      *            {@link org.kohsuke.args4j.Option} and
  82.      *            {@link org.kohsuke.args4j.Argument}. this object will receive
  83.      *            values.
  84.      * @param repo
  85.      *            repository this parser can translate options through.
  86.      * @throws IllegalAnnotationError
  87.      *             if the option bean class is using args4j annotations
  88.      *             incorrectly.
  89.      */
  90.     public CmdLineParser(Object bean, Repository repo) {
  91.         super(bean);
  92.         if (bean instanceof TextBuiltin) {
  93.             cmd = (TextBuiltin) bean;
  94.         }
  95.         if (repo == null && cmd != null) {
  96.             repo = cmd.getRepository();
  97.         }
  98.         this.db = repo;
  99.     }

  100.     /** {@inheritDoc} */
  101.     @Override
  102.     public void parseArgument(String... args) throws CmdLineException {
  103.         final ArrayList<String> tmp = new ArrayList<>(args.length);
  104.         for (int argi = 0; argi < args.length; argi++) {
  105.             final String str = args[argi];
  106.             if (str.equals("--")) { //$NON-NLS-1$
  107.                 while (argi < args.length)
  108.                     tmp.add(args[argi++]);
  109.                 break;
  110.             }

  111.             if (str.startsWith("--")) { //$NON-NLS-1$
  112.                 final int eq = str.indexOf('=');
  113.                 if (eq > 0) {
  114.                     tmp.add(str.substring(0, eq));
  115.                     tmp.add(str.substring(eq + 1));
  116.                     continue;
  117.                 }
  118.             }

  119.             tmp.add(str);

  120.             if (containsHelp(args)) {
  121.                 // suppress exceptions on required parameters if help is present
  122.                 seenHelp = true;
  123.                 // stop argument parsing here
  124.                 break;
  125.             }
  126.         }
  127.         List<OptionHandler> backup = null;
  128.         if (seenHelp) {
  129.             backup = unsetRequiredOptions();
  130.         }

  131.         try {
  132.             super.parseArgument(tmp.toArray(new String[0]));
  133.         } catch (Die e) {
  134.             if (!seenHelp) {
  135.                 throw e;
  136.             }
  137.             printToErrorWriter(CLIText.fatalError(e.getMessage()));
  138.         } finally {
  139.             // reset "required" options to defaults for correct command printout
  140.             if (backup != null && !backup.isEmpty()) {
  141.                 restoreRequiredOptions(backup);
  142.             }
  143.             seenHelp = false;
  144.         }
  145.     }

  146.     private void printToErrorWriter(String error) {
  147.         if (cmd == null) {
  148.             System.err.println(error);
  149.         } else {
  150.             try {
  151.                 cmd.getErrorWriter().println(error);
  152.             } catch (IOException e1) {
  153.                 System.err.println(error);
  154.             }
  155.         }
  156.     }

  157.     private List<OptionHandler> unsetRequiredOptions() {
  158.         List<OptionHandler> options = getOptions();
  159.         List<OptionHandler> backup = new ArrayList<>(options);
  160.         for (Iterator<OptionHandler> iterator = options.iterator(); iterator
  161.                 .hasNext();) {
  162.             OptionHandler handler = iterator.next();
  163.             if (handler.option instanceof NamedOptionDef
  164.                     && handler.option.required()) {
  165.                 iterator.remove();
  166.             }
  167.         }
  168.         return backup;
  169.     }

  170.     private void restoreRequiredOptions(List<OptionHandler> backup) {
  171.         List<OptionHandler> options = getOptions();
  172.         options.clear();
  173.         options.addAll(backup);
  174.     }

  175.     /**
  176.      * Check if array contains help option
  177.      *
  178.      * @param args
  179.      *            non null
  180.      * @return true if the given array contains help option
  181.      * @since 4.2
  182.      */
  183.     protected boolean containsHelp(String... args) {
  184.         return TextBuiltin.containsHelp(args);
  185.     }

  186.     /**
  187.      * Get the repository this parser translates values through.
  188.      *
  189.      * @return the repository, if specified during construction.
  190.      */
  191.     public Repository getRepository() {
  192.         if (db == null)
  193.             throw new IllegalStateException(CLIText.get().noGitRepositoryConfigured);
  194.         return db;
  195.     }

  196.     /**
  197.      * Get the revision walker used to support option parsing.
  198.      *
  199.      * @return the revision walk used by this option parser.
  200.      */
  201.     public RevWalk getRevWalk() {
  202.         if (walk == null)
  203.             walk = new RevWalk(getRepository());
  204.         return walk;
  205.     }

  206.     /**
  207.      * Get the revision walker used to support option parsing.
  208.      * <p>
  209.      * This method does not initialize the RevWalk and may return null.
  210.      *
  211.      * @return the revision walk used by this option parser, or null.
  212.      */
  213.     public RevWalk getRevWalkGently() {
  214.         return walk;
  215.     }

  216.     class MyOptionDef extends OptionDef {

  217.         public MyOptionDef(OptionDef o) {
  218.             super(o.usage(), o.metaVar(), o.required(), o.help(), o.hidden(),
  219.                     o.handler(), o.isMultiValued());
  220.         }

  221.         @Override
  222.         public String toString() {
  223.             if (metaVar() == null)
  224.                 return "ARG"; //$NON-NLS-1$
  225.             try {
  226.                 Field field = CLIText.class.getField(metaVar());
  227.                 String ret = field.get(CLIText.get()).toString();
  228.                 return ret;
  229.             } catch (Exception e) {
  230.                 e.printStackTrace(System.err);
  231.                 return metaVar();
  232.             }
  233.         }

  234.         @Override
  235.         public boolean required() {
  236.             return seenHelp ? false : super.required();
  237.         }
  238.     }

  239.     /** {@inheritDoc} */
  240.     @Override
  241.     protected OptionHandler createOptionHandler(OptionDef o, Setter setter) {
  242.         if (o instanceof NamedOptionDef) {
  243.             return super.createOptionHandler(o, setter);
  244.         }
  245.         return super.createOptionHandler(new MyOptionDef(o), setter);

  246.     }

  247.     /** {@inheritDoc} */
  248.     @Override
  249.     public void printSingleLineUsage(Writer w, ResourceBundle rb) {
  250.         List<OptionHandler> options = getOptions();
  251.         if (options.isEmpty()) {
  252.             super.printSingleLineUsage(w, rb);
  253.             return;
  254.         }
  255.         List<OptionHandler> backup = new ArrayList<>(options);
  256.         boolean changed = sortRestOfArgumentsHandlerToTheEnd(options);
  257.         try {
  258.             super.printSingleLineUsage(w, rb);
  259.         } finally {
  260.             if (changed) {
  261.                 options.clear();
  262.                 options.addAll(backup);
  263.             }
  264.         }
  265.     }

  266.     private boolean sortRestOfArgumentsHandlerToTheEnd(
  267.             List<OptionHandler> options) {
  268.         for (int i = 0; i < options.size(); i++) {
  269.             OptionHandler handler = options.get(i);
  270.             if (handler instanceof RestOfArgumentsHandler
  271.                     || handler instanceof PathTreeFilterHandler) {
  272.                 options.remove(i);
  273.                 options.add(handler);
  274.                 return true;
  275.             }
  276.         }
  277.         return false;
  278.     }
  279. }