/** * $Id$ * $Date$ * */ package org.xmlsh.builtin.commands; import java.io.IOException; import java.util.List; import org.xmlsh.annotations.Command; import org.xmlsh.core.BuiltinCommand; import org.xmlsh.core.InvalidArgumentException; import org.xmlsh.core.UnexpectedException; import org.xmlsh.core.XValue; import org.xmlsh.sh.shell.Shell; import org.xmlsh.types.TypeFamily; import org.xmlsh.util.Util; /* * The "test" command implements a subset of the unix test (1) command. * aka "[" */ /*************************************************************** * DESCRIPTION * Exit with the status determined by EXPRESSION. * * * An omitted EXPRESSION defaults to false. Otherwise, EXPRESSION is true * or false and sets exit status. It is one of: * * ( EXPRESSION ) * EXPRESSION is true * * ! EXPRESSION * EXPRESSION is false * * EXPRESSION1 -a EXPRESSION2 * both EXPRESSION1 and EXPRESSION2 are true * * EXPRESSION1 -o EXPRESSION2 * either EXPRESSION1 or EXPRESSION2 is true * * -n STRING * if a string the length of STRING is nonzero or * if xml then the sequence is not the null sequence * * * STRING equivalent to -n STRING * * -z STRING * the length of STRING is zero * * -S value * true if the value/argument is a string (not an xml type) * * -X value * true if the value/argument is an xml type * * -D name * true if the environment variable "name" is defined * * * STRING1 = STRING2 * the strings are equal * * STRING1 != STRING2 * the strings are not equal * * INTEGER1 -eq INTEGER2 * INTEGER1 is equal to INTEGER2 * * INTEGER1 -ge INTEGER2 * INTEGER1 is greater than or equal to INTEGER2 * * INTEGER1 -gt INTEGER2 * INTEGER1 is greater than INTEGER2 * * INTEGER1 -le INTEGER2 * INTEGER1 is less than or equal to INTEGER2 * * INTEGER1 -lt INTEGER2 * INTEGER1 is less than INTEGER2 * * INTEGER1 -ne INTEGER2 * INTEGER1 is not equal to INTEGER2 * * FILE1 -ef FILE2 * FILE1 and FILE2 have the same cananocal path * * FILE1 -nt FILE2 * FILE1 is newer (modification date) than FILE2 * * FILE1 -ot FILE2 * FILE1 is older than FILE2 * * -b FILE * FILE exists and is a "partition" (similar to block device) * * -d FILE * FILE exists and is a directory * * -e FILE * FILE exists * * -f FILE * FILE exists and is a regular file * * -r FILE * FILE exists and read permission is granted * * -s FILE * FILE exists and has a size greater than zero * * -w FILE * FILE exists and write permission is granted * * -x FILE * FILE exists and execute (or search) permission is granted * * -u string * TRUE if string is formated as an absolute URI (starts with <scheme>:// ) * * * Beware that parentheses need to be escaped (e.g., by back- * slashes) for shells. INTEGER may also be -l STRING, which evaluates to * the length of STRING. ******************************************************/ @Command(names = { "[", "test" }) public class test extends BuiltinCommand { private int pdepth = 0; @SuppressWarnings("serial") private static class Error extends Exception { public Error(final String message) { super(message); } }; private boolean eval(List<XValue> av) throws InvalidArgumentException, UnexpectedException, Error, IOException { if(av.size() == 0) return false; XValue av1 = av.remove(0); if(av.size() == 0) return evalUnary("-n", av1); if(pdepth > 0 && av.get(0).equals(")")) { pdepth--; av.remove(0); return evalUnary("-n", av1); } if(av1.isAtomic()) { String a1 = av1.toString(); if(a1.equals("!")) return !eval(av); if(a1.equals("(")) { pdepth++; boolean ret = eval(av); if(av.size() < 1) return ret; if(pdepth > 0 && (av.get(0).isAtomic() && av.get(0).toString() .equals(")"))) { av.remove(0); pdepth--; return ret; } } else if(a1.startsWith("-") && !Util.isInt(a1, true)) { if(av.size() < 1) { throw new Error("expected arg after " + a1); } return evalUnary(a1, av.remove(0)); } else if(av.size() == 1) { throw new Error("unexpected arg: " + av.remove(0)); } } XValue op = av.remove(0); if(op.isAtomic()) { if(av.size() < 1) throw new Error("Expected operator"); return evalBinary(av1, op.toString(), av.remove(0)); } else throw new Error("Unexpected xml value operator"); } private boolean evalBinary(XValue av1, String op, XValue value) throws Error, IOException { if(op.equals("=")) return av1.equals(value); else if(op.equals("!=")) return !av1.equals(value); else if(op.equals("-eq")) return compareInt(av1, value) == 0; else if(op.equals("-ne")) return compareInt(av1, value) != 0; else if(op.equals("-gt")) return compareInt(av1, value) > 0; else if(op.equals("-ge")) return compareInt(av1, value) >= 0; else if(op.equals("-lt")) return compareInt(av1, value) < 0; else if(op.equals("-le")) return compareInt(av1, value) <= 0; else if(op.equals("-ef")) return getFile(av1).compareTo(getFile(value)) == 0; else if(op.equals("-nt")) return getFile(av1).lastModified() > getFile(value).lastModified(); else if(op.equals("-ot")) return getFile(av1).lastModified() < getFile(value).lastModified(); else throw new Error("Invalid binary operator " + op); } private int compareInt(XValue a1, XValue a2) throws Error { if(!(a1.isAtomic() && a2.isAtomic())) throw new Error("args must be atomic expressions"); String s1 = a1.toString(); String s2 = a2.toString(); if(!Util.isInt(s1, true) || !Util.isInt(s2, true)) { throw new Error("Invalid integer expression"); } return Util.parseInt(s1, 0) - Util.parseInt(s2, 0); } private boolean evalUnary(String op, XValue value) throws InvalidArgumentException, UnexpectedException, IOException, Error { /* try type tests first */ if(op.equals("-X")) return value.isTypeFamily(TypeFamily.XDM); else if(op.equals("-S")) return value.isAtomic(); else if(op.equals("-D")) return mShell.getEnv().isDefined(value.toString()); if(op.equals("-n")) { // return ! value.isEmpty() ; if(value.isString()) return !value.toString().isEmpty(); return value.toBoolean(); } else if(op.equals("-z")) { // -z is opposite of -n // returns true if null,r if(value.isString()) return value.toString().isEmpty(); return !value.toBoolean(); // return value.isEmpty() ; } else if(op.equals("-b")) return getFile(value).getTotalSpace() > 0L; else if(op.equals("-d")) return getFile(value).isDirectory(); else if(op.equals("-e")) return getFile(value).exists(); else if(op.equals("-f")) return getFile(value).isFile(); else if(op.equals("-r")) return getFile(value).canRead(); else if(op.equals("-s")) return getFile(value).length() > 0; else if(op.equals("-w")) return getFile(value).canWrite(); else if(op.equals("-x")) return getFile(value).canExecute(); else if(op.equals("-u")) return isURI(value); else if(op.equals("-h")) return getFile(value).isHidden(); else { throw new Error("unknown test " + op); } } private boolean isURI(XValue value) { return value.toString().matches("^[a-z]+://.*"); } @Override public int run(List<XValue> args) throws Exception { List<XValue> av = args; if(getName().equals("[")) { if(av.size() == 0 || !av.remove(av.size() - 1).equals("]")) { mShell.printErr("Unbalanced ["); return 1; } } boolean ret = false; boolean bFirst = true; try { while(av.size() > 0) { String op = null; XValue a1 = av.get(0); if(!bFirst && a1.isAtomic() && (a1.equals("-a") || a1.equals("-o"))) { op = a1.toString(); av.remove(0); } boolean r = eval(av); if(op != null) { if(op.equals("-a")) ret = ret && r; else if(op.equals("-o")) ret = ret || r; else throw new Error("Invalid op " + op); } else ret = r; bFirst = false; } } catch (Error e) { mShell.printErr(getName() + ":" + e.getMessage()); return 1; } return Shell.fromBool(ret); } } // // // Copyright (C) 2008-2014 David A. Lee. // // The contents of this file are subject to the "Simplified BSD License" (the // "License"); // you may not use this file except in compliance with the License. You may // obtain a copy of the // License at http://www.opensource.org/licenses/bsd-license.php // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. // See the License for the specific language governing rights and limitations // under the License. // // The Original Code is: all this file. // // The Initial Developer of the Original Code is David A. Lee // // Portions created by (your name) are Copyright (C) (your legal entity). All // Rights Reserved. // // Contributor(s): none. //