/** * $Id: $ * $Date: $ * */ package org.xmlsh.sh.core; import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.HIDDEN_NAME; import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.HIDDEN_SYS; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.xmlsh.core.CoreException; import org.xmlsh.core.EvalEnv; import org.xmlsh.core.EvalFlag; import org.xmlsh.core.InvalidArgumentException; import org.xmlsh.core.XValue; import org.xmlsh.core.XVariable; import org.xmlsh.core.XVariableExpr; import org.xmlsh.json.JSONUtils; import org.xmlsh.sh.shell.Expander; import org.xmlsh.sh.shell.ParseResult; import org.xmlsh.sh.shell.Shell; import org.xmlsh.sh.shell.ShellConstants; import org.xmlsh.types.TypeFamily; import org.xmlsh.types.xtypes.XValueList; import org.xmlsh.util.FileUtils; import org.xmlsh.util.PathMatchOptions; import org.xmlsh.util.UnifiedFileAttributes; import org.xmlsh.util.Util; import org.xmlsh.util.XMLUtils; public class EvalUtils { static Logger mLogger = LogManager.getLogger(); /* * Eval a var expression ${ [ prefix ] varname [ '[' ind ']' ] [':' suffix ] } */ public static XValue evalVar(Shell shell, EvalEnv env, XVariableExpr expr) throws IOException, CoreException { XVariable var = shell.getEnv().getVar(expr.getName()); if(var == null) { // Special case ${#x} == 0 if x is undef if(Util.isEqual(expr.getPrefix(), "#")) { return XValue.newXValue(0); } return null; } // ${#var} notation if(Util.isEqual(expr.getPrefix(), "#")) { return XValue.newXValue(var.getSize()); } if(Util.isBlank(expr.getIndex()) && Util.isBlank(expr.getField())) return var.getValue(); else return var.getValue(shell, env, expr.getIndex(), expr.getField()); } /* * Evaluate a variable expression and extract its value */ private static XVariableExpr parseVarExpr(Shell shell, EvalEnv env, String varname) throws IOException, CoreException { mLogger.entry(shell, env, varname); XVariableExpr expr = new XVariableExpr(); // ${#var} notation if(varname.startsWith("#") && varname.length() > 1) { varname = varname.substring(1); expr.setPrefix("#"); } // Get the XVariable String ind = null; // [ind] expr String field = null; // :tie expr // Strip off tie expr if(varname.contains(":")) { int as = varname.indexOf(':'); if(as > 0) { expr.setField(varname.substring(as + 1)); varname = varname.substring(0, as); } } // Look for array notation // ${var[3]} if(varname.contains("[")) { int as = varname.indexOf('['); ind = varname.substring(as + 1, varname.indexOf(']')).trim(); // Special case - expand index as variables ... really shouldnt do this if(ind.startsWith("$")) ind = expandStringToString(shell, ind, env); expr.setIndex(ind); varname = varname.substring(0, as); } expr.setName(varname); return mLogger.exit(expr); } /* * Recursively Expand a possibly multi-level wildcard rooted at a directory */ public static List<String> expandDir(File dir, org.xmlsh.util.PathMatchOptions matchOptions) throws IOException { mLogger.entry(dir, matchOptions); ArrayList<String> results = new ArrayList<String>(); Path path = FileUtils.asValidPath(dir); if(path == null) { return mLogger.exit(results); } /* * Hack to handle 8.3 windows file names like "Local~1" * If not matched and this is windows * try an exact match to the canonical expanson of the dir and wild * * if(bIsWindows && swild.indexOf(0, '~' ) >= 0) { * File fwild = new File(dir, swild); * if(fwild.exists()) { * results.add(swild); * return mLogger.exit(results) * ; * } * } */ /* * If path isnt a directory then path/wild shouldnt expand just return * literally */ if(!Files.isDirectory(path, FileUtils.pathLinkOptions(true))) { mLogger.trace("path isnt directory {}", path); return mLogger.exit(results); } // If the glob matches a file exactly then choose it - depending on the // options assert (matchOptions.isNameMatcher()); if(matchOptions.isLiteralNameMatch()) { Path f = path.resolve(matchOptions.getNameString()); UnifiedFileAttributes attrs = FileUtils.getUnifiedFileAttributes(f, LinkOption.NOFOLLOW_LINKS); if(matchOptions.doVisit(f, attrs)) { String name = f.getFileName().toString(); results.add(name); } } else if(matchOptions.isPatternNameMatch()) { // final PathMatcher wp = Util.compileWild( path.getFileSystem() , wild, // CharAttrs.constInstance(CharAttr.ATTR_ESCAPED) , caseSensitive); mLogger.trace("opening a directory stream on: {}", path); try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(path)) { for(Path f : dirStream) { UnifiedFileAttributes attrs = FileUtils.getUnifiedFileAttributes(f, LinkOption.NOFOLLOW_LINKS); if(matchOptions.doVisit(f, attrs)) { String name = f.getFileName().toString(); results.add(name); } } } } if(results.size() == 0) return mLogger.exit(null); Collections.sort(results); return mLogger.exit(results); } public static void expandDir(File dir, String parent, CharAttributeBuffer wilds[], List<String> results) throws IOException { mLogger.entry(dir, parent, wilds, results); CharAttributeBuffer wild = wilds[0]; if(wilds.length < 2) wilds = null; else wilds = Arrays.copyOfRange(wilds, 1, wilds.length); assert (!wild.isEmpty()); PathMatchOptions matchOpts = null; if(Util.containsWild(wild)) { Pattern pattern = Util.compileWild(wild, FileUtils.isFilesystemCaseSensitive()); matchOpts = (new PathMatchOptions()).withWildMatching(pattern); } else { String fname = wild.decodeString(); // Last path segment with no wilds - only match if passes match test if(wilds == null) { matchOpts = (new PathMatchOptions()).withNameMatching(fname); } else { String path = parent == null ? fname : parent + (parent.endsWith("/") ? "" : "/") + fname; expandDir(new File(dir, fname), path, wilds, results); mLogger.exit(); return; } } assert (matchOpts != null); // If wild literaly starts with a . then dont hide hidden files if(wild.charAt(0) != ShellConstants.kDOT_CHAR) matchOpts = matchOpts.withFlagsHidden(HIDDEN_SYS, HIDDEN_NAME); List<String> rs = EvalUtils.expandDir(dir, matchOpts); if(rs == null) return; for(String r : rs) { String path = parent == null ? r : parent + (parent.endsWith("/") ? "" : "/") + r; if(wilds == null) results.add(path); else expandDir(new File(dir, r), path, wilds, results); } mLogger.exit(); } public static ParseResult expandStringToResult(Shell shell, String value, EvalEnv env, ParseResult result) throws IOException, CoreException { Expander e = new Expander(shell); return e.expandStringToResult(value, env, result == null ? new ParseResult() : result); } public static ParseResult expandValueToResult(Shell shell, XValue xv, EvalEnv env, ParseResult result) throws IOException, CoreException { Expander e = new Expander(shell); return e.expandValueToResult(xv, env, result == null ? new ParseResult() : result); } public static List<XValue> expandResultToList(Shell shell, ParseResult result, EvalEnv env) throws IOException, CoreException { Expander e = new Expander(shell); return e.expandResultToList(env, result); } public static List<XValue> expandValueToList(Shell shell, XValue xv, EvalEnv env) throws IOException, CoreException { Expander e = new Expander(shell); return e.expandResultToList(env, e.expandValueToResult(xv, env, new ParseResult())); } public static List<XValue> expandStringToList(Shell shell, String s, EvalEnv env) throws IOException, CoreException { Expander e = new Expander(shell); return e.expandStringToList(s, env); } public static String expandStringToString(Shell shell, String value, EvalEnv env) throws IOException, CoreException { List<XValue> ret = expandStringToList(shell, value, env); if(ret.size() == 0) return ""; else if(ret.size() == 1) return ret.get(0).toString(); return Util.joinValues(ret, ShellConstants.ARG_SEPARATOR); } // Expand a word and return as a single XValue // Preserves sequences and expands public static XValue expandStringToValue(Shell shell, String value, EvalEnv env) throws IOException, CoreException { List<XValue> ret = expandStringToList(shell, value, env); return expandListToValue(env, ret); } // Converts a List<XValue> into single XValue public static XValue expandListToValue(EvalEnv env, List<XValue> ret) { if(ret == null || ret.isEmpty()) return env.omitNulls() ? XValue.nullValue() : XValue.empytSequence(); else if(ret.size() == 1) return ret.get(0); return XValue.newXValue(ret); } public static ParseResult expandListToResult(Shell shell, List<XValue> list, EvalEnv env) throws IOException, CoreException { Expander e = new Expander(shell); ParseResult result = new ParseResult(); for(XValue xv : list) result = e.expandValueToResult(xv, env, result); return result; } public static XValue expandResultToValue(Shell shell, ParseResult result, EvalEnv env) throws IOException, CoreException { List<XValue> ret = expandResultToList(shell, result, env); return expandListToValue(env, ret); } public static int readToMatching(String arg, int i, StringBuffer sbv, char match) { char start = arg.charAt(i++); int matchCount = 1; // Eat up to match char '}' for(; i < arg.length(); i++) { char c = arg.charAt(i); if(c == match) { if(--matchCount == 0) break; } else if(c == start) matchCount++; sbv.append(c); } return i; } public static XValue splitStringToValue(Shell shell, String word, EvalEnv env) throws IOException { assert (word != null); if(word == null | word.isEmpty()) return XValue.newXValue(word); // if expand word then need to do IFS splitting if(env.expandWords() && !env.preserveValue()) return XValue.newXValue((String[]) shell.getIFS().split(word).toArray()); else return XValue.newXValue(word); } public static ParseResult splitStringToResult(Shell shell, String word, EvalEnv env, ParseResult result) throws IOException, CoreException { Expander e = new Expander(shell); // if expand word then need to do IFS splitting if(env.expandWords() && !env.preserveValue()) { for(String s : shell.getIFS().split(word)) result = e.expandStringToResult(s, env, result); } else e.expandStringToResult(word, env, result); return result; } /* * Evaluate a variable and return either a list of zero or more values */ public static ParseResult evalVarToResult(Shell shell, XVariableExpr expr, EvalEnv env, CharAttrs attr, ParseResult result) throws IOException, CoreException { mLogger.entry(shell, expr, env, attr, result); List<XValue> vs = null; // TODO: Special case of $@ in quotes boolean dollarAt = expr.getName().equals("@"); if(attr.isQuote() && dollarAt) vs = shell.getArgs(); else { XValue v = evalVar(shell, env, expr); if(v == null) return result; // Non tong null values go away else if(!attr.isPreserve() && v.isNull()) vs = null; if(!isExpandable(v, env)) vs = Collections.singletonList(v); else if(!attr.isQuote() && (v.isSequence() || !env.expandAny())) { // $* // $@ // or // other // sequence // like // lists vs = v.asXList(); } else { List<String> fields; if(attr.isQuote() || !isExpandable(v, env)) vs = Collections.singletonList(v); else { String s = v.toString(); if(env.expandWords()) { // Extract fields fields = shell.getIFS().split(s); if(Util.isEmpty(fields)) vs = null; // Try to preserve original value else if(fields.size() == 1 && Util.isEqual(fields.get(0), s)) vs = Collections.singletonList(v); else { vs = new ArrayList<XValue>(fields.size()); for(String f : fields) { if(Util.isEmpty(f)) continue; vs.add(XValue.newXValue(f)); } } } else vs = Collections.singletonList(XValue.newXValue(s)); } } } // Append the first value to any previous content in the arg // N..last-1 become new args // Last is a new word but possibly appendable if(vs != null) { int vsize = vs.size(); for(int vi = 0; vi < vsize; vi++) { XValue xv = vs.get(vi); if(vi > 0) result.flush(); result.append(xv, env, attr); } } return mLogger.exit(result); } // What kinds of values do we peek into private static boolean isExpandable(XValue v, EvalEnv env) { if(env.preserveValue() || !env.expandAny()) return false; /* * if( v.isAtomic() ) * return true ; * * // HACK * // * if( v.isXdmValue() || (v.isXType() && * v.isInstanceOf(XValueSequence.class) ) ) * return true; * * return false ; */ return v.isAtomic() || v.isSequence(); } public static ParseResult evalVarToResult(Shell shell, String var, EvalEnv env, CharAttrs attr, ParseResult result) throws IOException, CoreException { mLogger.entry(shell, var, env, attr, result); if(Util.isOneOf(var, "*", "@")) { XVariableExpr expr = new XVariableExpr(); expr.setName(var); return evalVarToResult(shell, expr, env.withFlagsOff( EvalFlag.SPLIT_WORDS, EvalFlag.EXPAND_VAR), attr, result); } XVariableExpr expr = parseVarExpr(shell, env, var); ParseResult res = evalVarToResult(shell, expr, env, attr, result); return mLogger.exit(res); } public static int getSize(XValue xvalue) throws InvalidArgumentException { if(xvalue == null || xvalue.isNull()) return 0; return xvalue.getTypeMethods().getSize(xvalue.asObject()); } public static XValue newContainerInstance(TypeFamily family) throws InvalidArgumentException { switch(family){ case XTYPE: case JAVA: return XValue.newXValue(family, new XValueList()); case JSON: return XValue.newXValue(family, JSONUtils.newJsonObject()); case XDM: return XValue.newXValue(family, XMLUtils.emptySequence()); } return XValue.nullValue(); } public static XValue getValues(EvalEnv env, XValue xvalue) throws InvalidArgumentException { if(xvalue == null || xvalue.isNull()) return XValue.nullValue(); List<XValue> xvs = xvalue.getTypeMethods().getXValues(xvalue.asObject()); return expandListToValue(env, xvs); } } /* * Copyright (C) 2008-2012 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): David A. Lee */