Exit Full View

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

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

public class ShShell implements Shell {

    static ShShell instance = new ShShell();

    /**
     * Given that a command is made up of an array of String parts, build the complete command line string.
     * It is intended to be used with the unix shell "sh" and the many variants, such as bash, dash etc.
     * <p>
     * If the Feather script contained :
     * <p>
     * $( echo 'Hello $name' )
     * <p>
     * Then there are THREE parts e.g. : "echo 'Hello ", "Colin O'Reilly" and "'"
     * The second part was from the feather variable "name", and contains a single quote.
     * From the first part, we see a quote before "Hello", so subsequent non-literal parts
     * must be escaped.
     * So when we get to the 2nd part, we escape the quote to form :
     * <p>
     * Colin O'\''Reilly
     * <p>
     * The 3rd part is a literal, so we don't attempt to escape it, and instead count the number
     * of quotes, and we find the end quote, so if there were more parts, these wouldn't be escaped.
     */
    @Override
    public String[] buildShell(CommandBase commandBase) {
        return new String[]{"sh", "-c", buildCommandString(commandBase)};
    }

    protected final String buildCommandString(CommandBase commandBase) {

        if (commandBase instanceof Pipe) {
            Pipe pipe = (Pipe) commandBase;
            return buildCommandString(pipe.producer) + " | " + buildCommandString(pipe.consumer);
        }

        if (commandBase instanceof Redirect) {
            Redirect redirect = (Redirect) commandBase;
            String result = buildCommandString(redirect.command);
            if (redirect.outFile != null) {
                if (redirect.appendOut) {
                    result += " >> " + quote(redirect.outFile.getPath());
                } else {
                    result += " > " + quote(redirect.outFile.getPath());
                }
            }
            if (redirect.errFile != null) {
                if (redirect.errFile == redirect.outFile) {
                    result += " 2>&1";
                } else {
                    if (redirect.appendErr) {
                        result += " 2>> " + quote(redirect.errFile.getPath());
                    } else {
                        result += " 2> " + quote(redirect.errFile.getPath());
                    }
                }
            }

            if (redirect.inFile != null) {
                result += " < " + quote(redirect.inFile.getPath());
            }

            return result;
        }

        if (!(commandBase instanceof Command)) {
            throw new IllegalArgumentException("Expected a Command, Pipe or Redirect, but found " + commandBase.getClass().getSimpleName());
        }

        Command command = (Command) commandBase;

        StringBuilder result = new StringBuilder();
        boolean needsEscaping = false;
        for (Command.CommandPart part : command.parts) {
            if (part.isLiteral) {
                result.append(part.str);
                int indexOfQuote = part.str.indexOf('\'');
                while (indexOfQuote >= 0) {
                    needsEscaping = !needsEscaping;
                    indexOfQuote = part.str.indexOf('\'', indexOfQuote + 1);
                }
            } else {
                if (needsEscaping) {
                    result.append(escape(part.str));
                } else {
                    result.append(part.str);
                }
            }
        }


        return result.toString();
    }

    private String quote(String str) {
        return "'" + escape(str) + "'";
    }

    private String escape(String str) {
        return str.replace("'", "'\\''");
    }

}