CmdLineParser.java

  1. /*
  2.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3.  * and other copyright owners as documented in the project's IP log.
  4.  *
  5.  * This program and the accompanying materials are made available
  6.  * under the terms of the Eclipse Distribution License v1.0 which
  7.  * accompanies this distribution, is reproduced below, and is
  8.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  9.  *
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or
  13.  * without modification, are permitted provided that the following
  14.  * conditions are met:
  15.  *
  16.  * - Redistributions of source code must retain the above copyright
  17.  *   notice, this list of conditions and the following disclaimer.
  18.  *
  19.  * - Redistributions in binary form must reproduce the above
  20.  *   copyright notice, this list of conditions and the following
  21.  *   disclaimer in the documentation and/or other materials provided
  22.  *   with the distribution.
  23.  *
  24.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  25.  *   names of its contributors may be used to endorse or promote
  26.  *   products derived from this software without specific prior
  27.  *   written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42.  */

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

  44. import java.io.IOException;
  45. import java.io.Writer;
  46. import java.lang.reflect.Field;
  47. import java.util.ArrayList;
  48. import java.util.Iterator;
  49. import java.util.List;
  50. import java.util.ResourceBundle;

  51. import org.eclipse.jgit.lib.ObjectId;
  52. import org.eclipse.jgit.lib.Repository;
  53. import org.eclipse.jgit.pgm.Die;
  54. import org.eclipse.jgit.pgm.TextBuiltin;
  55. import org.eclipse.jgit.pgm.internal.CLIText;
  56. import org.eclipse.jgit.revwalk.RevCommit;
  57. import org.eclipse.jgit.revwalk.RevTree;
  58. import org.eclipse.jgit.revwalk.RevWalk;
  59. import org.eclipse.jgit.transport.RefSpec;
  60. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  61. import org.kohsuke.args4j.CmdLineException;
  62. import org.kohsuke.args4j.IllegalAnnotationError;
  63. import org.kohsuke.args4j.NamedOptionDef;
  64. import org.kohsuke.args4j.OptionDef;
  65. import org.kohsuke.args4j.OptionHandlerRegistry;
  66. import org.kohsuke.args4j.spi.OptionHandler;
  67. import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
  68. import org.kohsuke.args4j.spi.Setter;

  69. /**
  70.  * Extended command line parser which handles --foo=value arguments.
  71.  * <p>
  72.  * The args4j package does not natively handle --foo=value and instead prefers
  73.  * to see --foo value on the command line. Many users are used to the GNU style
  74.  * --foo=value long option, so we convert from the GNU style format to the
  75.  * args4j style format prior to invoking args4j for parsing.
  76.  */
  77. public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser {
  78.     static {
  79.         OptionHandlerRegistry registry = OptionHandlerRegistry.getRegistry();
  80.         registry.registerHandler(AbstractTreeIterator.class,
  81.                 AbstractTreeIteratorHandler.class);
  82.         registry.registerHandler(ObjectId.class, ObjectIdHandler.class);
  83.         registry.registerHandler(RefSpec.class, RefSpecHandler.class);
  84.         registry.registerHandler(RevCommit.class, RevCommitHandler.class);
  85.         registry.registerHandler(RevTree.class, RevTreeHandler.class);
  86.         registry.registerHandler(List.class, OptionWithValuesListHandler.class);
  87.     }

  88.     private final Repository db;

  89.     private RevWalk walk;

  90.     private boolean seenHelp;

  91.     private TextBuiltin cmd;

  92.     /**
  93.      * Creates a new command line owner that parses arguments/options and set
  94.      * them into the given object.
  95.      *
  96.      * @param bean
  97.      *            instance of a class annotated by
  98.      *            {@link org.kohsuke.args4j.Option} and
  99.      *            {@link org.kohsuke.args4j.Argument}. this object will receive
  100.      *            values.
  101.      * @throws IllegalAnnotationError
  102.      *             if the option bean class is using args4j annotations
  103.      *             incorrectly.
  104.      */
  105.     public CmdLineParser(Object bean) {
  106.         this(bean, null);
  107.     }

  108.     /**
  109.      * Creates a new command line owner that parses arguments/options and set
  110.      * them into the given object.
  111.      *
  112.      * @param bean
  113.      *            instance of a class annotated by
  114.      *            {@link org.kohsuke.args4j.Option} and
  115.      *            {@link org.kohsuke.args4j.Argument}. this object will receive
  116.      *            values.
  117.      * @param repo
  118.      *            repository this parser can translate options through.
  119.      * @throws IllegalAnnotationError
  120.      *             if the option bean class is using args4j annotations
  121.      *             incorrectly.
  122.      */
  123.     public CmdLineParser(Object bean, Repository repo) {
  124.         super(bean);
  125.         if (bean instanceof TextBuiltin) {
  126.             cmd = (TextBuiltin) bean;
  127.         }
  128.         if (repo == null && cmd != null) {
  129.             repo = cmd.getRepository();
  130.         }
  131.         this.db = repo;
  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     public void parseArgument(String... args) throws CmdLineException {
  136.         final ArrayList<String> tmp = new ArrayList<>(args.length);
  137.         for (int argi = 0; argi < args.length; argi++) {
  138.             final String str = args[argi];
  139.             if (str.equals("--")) { //$NON-NLS-1$
  140.                 while (argi < args.length)
  141.                     tmp.add(args[argi++]);
  142.                 break;
  143.             }

  144.             if (str.startsWith("--")) { //$NON-NLS-1$
  145.                 final int eq = str.indexOf('=');
  146.                 if (eq > 0) {
  147.                     tmp.add(str.substring(0, eq));
  148.                     tmp.add(str.substring(eq + 1));
  149.                     continue;
  150.                 }
  151.             }

  152.             tmp.add(str);

  153.             if (containsHelp(args)) {
  154.                 // suppress exceptions on required parameters if help is present
  155.                 seenHelp = true;
  156.                 // stop argument parsing here
  157.                 break;
  158.             }
  159.         }
  160.         List<OptionHandler> backup = null;
  161.         if (seenHelp) {
  162.             backup = unsetRequiredOptions();
  163.         }

  164.         try {
  165.             super.parseArgument(tmp.toArray(new String[0]));
  166.         } catch (Die e) {
  167.             if (!seenHelp) {
  168.                 throw e;
  169.             }
  170.             printToErrorWriter(CLIText.fatalError(e.getMessage()));
  171.         } finally {
  172.             // reset "required" options to defaults for correct command printout
  173.             if (backup != null && !backup.isEmpty()) {
  174.                 restoreRequiredOptions(backup);
  175.             }
  176.             seenHelp = false;
  177.         }
  178.     }

  179.     private void printToErrorWriter(String error) {
  180.         if (cmd == null) {
  181.             System.err.println(error);
  182.         } else {
  183.             try {
  184.                 cmd.getErrorWriter().println(error);
  185.             } catch (IOException e1) {
  186.                 System.err.println(error);
  187.             }
  188.         }
  189.     }

  190.     private List<OptionHandler> unsetRequiredOptions() {
  191.         List<OptionHandler> options = getOptions();
  192.         List<OptionHandler> backup = new ArrayList<>(options);
  193.         for (Iterator<OptionHandler> iterator = options.iterator(); iterator
  194.                 .hasNext();) {
  195.             OptionHandler handler = iterator.next();
  196.             if (handler.option instanceof NamedOptionDef
  197.                     && handler.option.required()) {
  198.                 iterator.remove();
  199.             }
  200.         }
  201.         return backup;
  202.     }

  203.     private void restoreRequiredOptions(List<OptionHandler> backup) {
  204.         List<OptionHandler> options = getOptions();
  205.         options.clear();
  206.         options.addAll(backup);
  207.     }

  208.     /**
  209.      * Check if array contains help option
  210.      *
  211.      * @param args
  212.      *            non null
  213.      * @return true if the given array contains help option
  214.      * @since 4.2
  215.      */
  216.     protected boolean containsHelp(String... args) {
  217.         return TextBuiltin.containsHelp(args);
  218.     }

  219.     /**
  220.      * Get the repository this parser translates values through.
  221.      *
  222.      * @return the repository, if specified during construction.
  223.      */
  224.     public Repository getRepository() {
  225.         if (db == null)
  226.             throw new IllegalStateException(CLIText.get().noGitRepositoryConfigured);
  227.         return db;
  228.     }

  229.     /**
  230.      * Get the revision walker used to support option parsing.
  231.      *
  232.      * @return the revision walk used by this option parser.
  233.      */
  234.     public RevWalk getRevWalk() {
  235.         if (walk == null)
  236.             walk = new RevWalk(getRepository());
  237.         return walk;
  238.     }

  239.     /**
  240.      * Get the revision walker used to support option parsing.
  241.      * <p>
  242.      * This method does not initialize the RevWalk and may return null.
  243.      *
  244.      * @return the revision walk used by this option parser, or null.
  245.      */
  246.     public RevWalk getRevWalkGently() {
  247.         return walk;
  248.     }

  249.     class MyOptionDef extends OptionDef {

  250.         public MyOptionDef(OptionDef o) {
  251.             super(o.usage(), o.metaVar(), o.required(), o.help(), o.hidden(),
  252.                     o.handler(), o.isMultiValued());
  253.         }

  254.         @Override
  255.         public String toString() {
  256.             if (metaVar() == null)
  257.                 return "ARG"; //$NON-NLS-1$
  258.             try {
  259.                 Field field = CLIText.class.getField(metaVar());
  260.                 String ret = field.get(CLIText.get()).toString();
  261.                 return ret;
  262.             } catch (Exception e) {
  263.                 e.printStackTrace(System.err);
  264.                 return metaVar();
  265.             }
  266.         }

  267.         @Override
  268.         public boolean required() {
  269.             return seenHelp ? false : super.required();
  270.         }
  271.     }

  272.     /** {@inheritDoc} */
  273.     @Override
  274.     protected OptionHandler createOptionHandler(OptionDef o, Setter setter) {
  275.         if (o instanceof NamedOptionDef)
  276.             return super.createOptionHandler(o, setter);
  277.         else
  278.             return super.createOptionHandler(new MyOptionDef(o), setter);

  279.     }

  280.     /** {@inheritDoc} */
  281.     @Override
  282.     public void printSingleLineUsage(Writer w, ResourceBundle rb) {
  283.         List<OptionHandler> options = getOptions();
  284.         if (options.isEmpty()) {
  285.             super.printSingleLineUsage(w, rb);
  286.             return;
  287.         }
  288.         List<OptionHandler> backup = new ArrayList<>(options);
  289.         boolean changed = sortRestOfArgumentsHandlerToTheEnd(options);
  290.         try {
  291.             super.printSingleLineUsage(w, rb);
  292.         } finally {
  293.             if (changed) {
  294.                 options.clear();
  295.                 options.addAll(backup);
  296.             }
  297.         }
  298.     }

  299.     private boolean sortRestOfArgumentsHandlerToTheEnd(
  300.             List<OptionHandler> options) {
  301.         for (int i = 0; i < options.size(); i++) {
  302.             OptionHandler handler = options.get(i);
  303.             if (handler instanceof RestOfArgumentsHandler
  304.                     || handler instanceof PathTreeFilterHandler) {
  305.                 options.remove(i);
  306.                 options.add(handler);
  307.                 return true;
  308.             }
  309.         }
  310.         return false;
  311.     }
  312. }