/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.shell.bjorne;
import java.io.IOException;
import java.io.Reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jnode.shell.ShellSyntaxException;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.ArgumentSyntax;
import org.jnode.shell.syntax.FlagArgument;
import org.jnode.shell.syntax.OptionSyntax;
import org.jnode.shell.syntax.OptionalSyntax;
import org.jnode.shell.syntax.RepeatSyntax;
import org.jnode.shell.syntax.SequenceSyntax;
import org.jnode.shell.syntax.SyntaxBundle;
/**
* This class implements the 'read' built-in.
*
* @author crawley@jnode.org
*/
final class ReadBuiltin extends BjorneBuiltin {
private static final SyntaxBundle SYNTAX =
new SyntaxBundle("read", new SequenceSyntax(
new OptionalSyntax(new OptionSyntax("noEscape", 'r')),
new RepeatSyntax(new ArgumentSyntax("varName"), 0, Integer.MAX_VALUE)));
static final Factory FACTORY = new Factory() {
public BjorneBuiltinCommandInfo buildCommandInfo(BjorneContext context) {
return new BjorneBuiltinCommandInfo("read", SYNTAX, new ReadBuiltin(context), context);
}
};
private final VariableNameArgument varNameArg;
private final FlagArgument noEscapeArg = new FlagArgument(
"noEscape", Argument.OPTIONAL, "if set, '\' does not escape a newline");
private final BjorneContext context;
Pattern ifsSplittingPattern;
Pattern ifsTrimmingPattern;
ReadBuiltin(BjorneContext context) {
super("Read a line of input and repopulate the shell 'args'");
this.context = context;
varNameArg = new VariableNameArgument(
"varName", context, Argument.OPTIONAL | Argument.MULTIPLE, "shell variables to be set");
registerArguments(noEscapeArg, varNameArg);
}
public void execute() throws Exception {
boolean escapeCheck = !noEscapeArg.isSet();
String line = readLine(getInput().getReader(), escapeCheck);
String[] varNames = varNameArg.getValues();
if (varNames.length > 0) {
String[] fields = extractFields(line, varNames.length);
for (int i = 0; i < varNames.length; i++) {
String value = (i >= fields.length || fields[i] == null) ? "" : fields[i];
context.getParent().setVariable(varNames[i], value);
}
}
}
private String[] extractFields(String line, int nosVars) throws ShellSyntaxException {
String ifs = context.variable("IFS");
if (ifs == null) {
ifs = " \t\n";
} else if (ifs.length() == 0) {
return new String[]{line};
}
String[] fields = new String[nosVars];
createIfsPatterns(ifs);
String content;
if (ifsTrimmingPattern != null) {
Matcher trimMatcher = ifsTrimmingPattern.matcher(line);
trimMatcher.matches();
content = trimMatcher.group(1);
} else {
content = line;
}
if (line.length() == 0) {
return new String[0];
}
Matcher fieldMatcher = null;
for (int i = 0; i < fields.length - 1; i++) {
if (fieldMatcher == null) {
fieldMatcher = ifsSplittingPattern.matcher(content);
} else {
fieldMatcher.reset(content);
}
if (fieldMatcher.matches()) {
fields[i] = fieldMatcher.group(1);
content = fieldMatcher.group(2);
} else {
fields[i] = content;
content = null;
break;
}
}
if (content != null) {
fields[fields.length - 1] = content;
}
return fields;
}
private void createIfsPatterns(String ifs) {
if (ifs.equals(" ") || ifs.equals("\t") || ifs.equals("\n")) {
ifsTrimmingPattern = Pattern.compile("[ \\t\\n]*(.*[^ \\t\\n])[ \\t\\n]*");
ifsSplittingPattern = Pattern.compile("([^ \\t\\n]+)[ \\t\\n]+([^ \\t\\n].*)");
} else {
// First separate the IFS into whitespace and non-whitespace characters,
// adding '\' escapes for any that characters that need to be escaped.
StringBuilder sb1 = new StringBuilder(4);
StringBuilder sb2 = new StringBuilder(4);
for (char ch : ifs.toCharArray()) {
switch (ch) {
case ' ':
sb1.append(' ');
break;
case '\t':
sb1.append("\\t");
break;
case '\n':
sb1.append("\\n");
break;
case '.':
case '?':
case '*':
case '+':
case '[':
case ']':
case '(':
case ')':
case '|':
case '{':
case '}':
case '\\':
case '^':
case '$':
case '-':
sb2.append('\\').append(ch);
break;
default:
sb2.append("\\\n");
break;
}
}
String ifsWhitespace = sb1.toString();
String ifsNonWhitespace = sb2.toString();
// If we have any IFS whitespace, create the pattern to trim it.
if (ifsWhitespace.length() == 0) {
ifsTrimmingPattern = null;
} else {
ifsTrimmingPattern = Pattern.compile(
"[" + ifsWhitespace + "]*(.*[^" + ifsWhitespace + "])[" + ifsWhitespace + "]*");
}
// Create the pattern to split a (possibly empty) field
if (ifsWhitespace.length() > 0 && ifsNonWhitespace.length() > 0) {
ifsSplittingPattern = Pattern.compile(
"([^" + ifsWhitespace + ifsNonWhitespace + "]*)[" + ifsWhitespace + "]*[" +
ifsNonWhitespace + "][" + ifsWhitespace + "]*(|[^" + ifsWhitespace + "].*)");
} else if (ifsWhitespace.length() > 0) {
ifsSplittingPattern = Pattern.compile(
"([^" + ifsWhitespace + "]*)[" + ifsWhitespace + "]+(|[^" + ifsWhitespace + "].*)");
} else {
ifsSplittingPattern = Pattern.compile(
"([^" + ifsNonWhitespace + "]*)[" + ifsNonWhitespace + "](.*)");
}
}
}
private String readLine(Reader reader, boolean escapeCheck) throws IOException {
StringBuilder sb = new StringBuilder(40);
int ch;
while ((ch = reader.read()) != -1 && ch != '\n') {
if (ch == '\\' && escapeCheck) {
ch = reader.read();
if (ch == -1) {
sb.append('\\');
break;
} else if (ch != '\n') {
sb.append('\\').append((char) ch);
}
} else {
sb.append((char) ch);
}
}
return sb.toString();
}
}