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;
}
}
}