Exit Full View

Feather2 / feather2-runtime / src / main / java / uk / co / nickthecoder / feather / runtime / command / Command.java

package uk.co.nickthecoder.feather.runtime.command;

/**
 * A Command is built up in the same way as smart Strings.
 * i.e. the string is parsed from plain text strings, and expressions.
 * Expression are in the form ${expression} or $reference.
 * <p>
 * To create a Command from within a Feather script use something like this :
 * </p>
 *
 * <pre>
 * $( echo Hello $name )
 * </pre>
 * <p>
 * This will NOT run the command, it will only create a Command object, so instead :
 * <pre>
 * $( echo Hello $name ).run()
 * </pre>
 *
 * <p>
 * You can also use .collect() or .eval() instead of .run().
 * </p>
 * <p>
 * If you are used to using Java's ProcessBuilder, forget what you know... Feather does things differently!
 * I want commands to as powerful as the Unix command line, including features such as redirection, piping,
 * glob wildcards, shell variables, aliases etc. So feather runs commands using a shell. The default shell is
 * "sh". Therefore the example above actually runs the program "sh" with 2 arguments "-c" and
 * "echo Hello Nick".
 * </p>
 * <p>
 * Because we are using a shell, it is important that arguments are escaped correctly.
 * The example above is WRONG. If "name" were "O'Reilly", then we would end up with :
 * </p>
 * <p>
 * sh -e echo Hello O'Reilly
 * </p>
 * <p>
 * And sh will moan about the unmatched quote. Here's the correction :
 * </p>
 * <pre>
 * $( echo Hello '$name' ).run()
 * </pre>
 * <p>
 * Feather does some magic behind the scenes... Whenever it finds an expression, such as $name, it
 * looks to see it we are currently inside single quotes by counting the number of single quote to the left of
 * the expression (in this case the count is odd (1), so we ARE inside a quoted region).
 * It then evaluates $name, and adjusts it accordingly. In this example, it builds :
 * </p>
 * <pre>
 * sh -c echo Hello 'O'\''Reilly'
 * </pre>
 * <p>
 * If you include double quotes, then Feather does nothing special, the command string is passed to sh
 * unchanged. So you could do :
 * </p>
 * <pre>
 * ls "foo.${extension}"
 * </pre>
 * <p>
 * If the feather variable extension contains the string "*", then the command will be :
 * </p>
 * <pre>
 * ls "foo.*"
 * </pre>
 * <p>
 * And the shell will see the * as a wildcard. However, if extension is a quote, or double quote,
 * the the command will fail. Therefore you can only use these kinds of quotes if you know for sure that the
 * feather variable will never contains any unexpected special characters.
 * </p>
 * <p>
 * Here's another example, this time echo is passed only one parameter, previously it was passed 2.
 * You are not limited to only one expression within the single quotes.
 * </p>
 * <pre>
 * $( echo 'Hello $forename $surname' ).run()
 * </pre>
 * <p>
 * Note. It is good practice to use the special argument "--" when building commands. For example :
 * </p>
 * <pre>
 * $( rm '$file' )
 * </pre>
 * <p>
 * will sometimes fail! If $file begins with a dash, then rm will think it is a flag, not a filename.
 * So instead, include "--" which tells rm that there are no more flags :
 * </p>
 * <pre>
 * $( rm -- '$file' )
 * </pre>
 * <p>
 * Now $file will be treated as a filename even if it being with a dash.
 * This isn't specific to Feather, you should do this for all your shell scripts ;-)
 * Most commands support the special "--" argument, but commands written by noobs won't!
 * </p>
 * <p>
 * If you want to use a different shell, then either specify it each time you run a command :
 * </p>
 * <pre>
 * $( echo Hello ).shell( myShell ).run()
 * </pre>
 * <p>
 * Or change the default shell once and for all :
 * </p>
 * <pre>
 * CommandRunner.defaultShell = myShell
 * $( echo Hello ).run()
 * // All future commands will also use myShell
 * </pre>
 */
public class Command extends CommandBase {

    public final CommandPart[] parts;

    public Command(CommandPart... commandParts) {
        this.parts = commandParts;
    }

    /**
     * A command part is simply a partial string when building the command. For example, the command :
     * <p>
     * $( echo Hello $name )
     * <p>
     * is made up of two parts.
     * The first part is plain text "echo Hello ".
     * The second is the result of the expression $name, and this time isLiteral = false.
     */
    public static class CommandPart {

        /**
         * The text for this part of the command line.
         * For an expression, this is the evaluation of the expression. For example $name may have the
         * value "Nick", not "name".
         */
        public final String str;

        /**
         * ShCommandLineBuilder uses isLiteral to decide if single quotes within the text should be escaped.
         * (Quotes are escaped only when isLiteral == false).
         */
        public final boolean isLiteral;

        public CommandPart(String part, boolean isLiteral) {
            this.str = part;
            this.isLiteral = isLiteral;
        }
    }
}