/*
* $Id$
*
* Copyright (C) 2003-2014 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.command.file;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import org.jnode.command.argument.NumberListArgument;
import org.jnode.command.util.IOUtils;
import org.jnode.command.util.NumberRange;
import org.jnode.shell.AbstractCommand;
import org.jnode.shell.syntax.FileArgument;
import org.jnode.shell.syntax.FlagArgument;
import org.jnode.shell.syntax.StringArgument;
/**
* Unix `cut` command
*
* TODO add --complement to select byte/chars/fields that are *not* within the given ranges
* TODO make byte-ranges multi-byte character friendly
* @author chris boertien
*/
public class CutCommand extends AbstractCommand {
private static final String help_byte = "Select only the listed bytes";
private static final String help_char = "Select only the listed chars";
private static final String help_field = "Select only the listed fields";
private static final String help_in_delim = "Use this for the delimeter instead of TAB";
private static final String help_out_delim = "Replace the input delimeter with this in the output";
private static final String help_files = "The files to operate on, or stdin if none are given";
private static final String help_suppress = "Do not output lines that do not contain a delimeter character";
private static final String help_complement = "Complement the set of selected bytes, chars or fields";
private static final String help_super = "Remove ranges of bytes, chars, or fields from input lines";
private static final String err_delim = "An delimeter may only be present when operating on fields.";
private static final String err_multi_mode = "Only one type of list may be specified";
private static final String err_no_mode = "Must select either a byte, char or field range";
private static final String err_suppress = "Suppression only makes sense when using fields";
private static final String fmt_err = "cut: %s%n";
private static enum Mode {
BYTE, CHAR, FIELD;
}
private final NumberListArgument argByteRange;
private final NumberListArgument argCharRange;
private final NumberListArgument argFieldRange;
private final StringArgument argInDelim;
private final StringArgument argOutDelim;
private final FileArgument argFiles;
private final FlagArgument argSuppress;
private final FlagArgument argComplement;
private PrintWriter err;
private BufferedWriter out;
private File[] files;
private Mode mode;
private NumberRange[] list;
private String inDelim;
private String outDelim;
private boolean suppress;
@SuppressWarnings("unused")
private boolean complement;
public CutCommand() {
super(help_super);
argByteRange = new NumberListArgument("byte-range", 0, 1, Integer.MAX_VALUE - 1, help_byte);
argCharRange = new NumberListArgument("char-range", 0, 1, Integer.MAX_VALUE - 1, help_char);
argFieldRange = new NumberListArgument("field-range", 0, 1, Integer.MAX_VALUE - 1, help_field);
argInDelim = new StringArgument("in-delim", 0, help_in_delim);
argOutDelim = new StringArgument("out-delim", 0, help_out_delim);
argFiles = new FileArgument("files", 0, help_files);
argSuppress = new FlagArgument("suppress", 0, help_suppress);
argComplement = new FlagArgument("complement", 0, help_complement);
registerArguments(argByteRange, argCharRange, argFieldRange, argInDelim, argOutDelim, argFiles);
registerArguments(argSuppress, argComplement);
}
public void execute() throws IOException {
err = getError().getPrintWriter();
out = new BufferedWriter(getOutput().getPrintWriter());
parseOptions();
BufferedReader reader;
List<String> lines;
for (File file : files) {
if (file.getName().equals("-")) {
reader = new BufferedReader(getInput().getReader());
} else {
reader = IOUtils.openBufferedReader(file);
}
try {
lines = IOUtils.readLines(reader);
} finally {
IOUtils.close(reader);
}
try {
if (mode == Mode.BYTE) {
cutBytes(lines);
} else if (mode == Mode.CHAR) {
cutChars(lines);
} else if (mode == Mode.FIELD) {
cutFields(lines);
}
} finally {
IOUtils.flush(out);
}
}
}
private void cutBytes(List<String> lines) throws IOException {
// FIXME
// In the case of single-byte characters, this is the right
// path to take, but if characters are multi-byte, then there
// is supposed to be aligning done to make sure a byte-range
// does not fall in the middle of a character.
cutChars(lines);
}
private void cutChars(List<String> lines) throws IOException {
int limit, start, end;
for (String line : lines) {
limit = line.length();
for (NumberRange range : list) {
start = Math.min(limit, range.start());
end = Math.min(limit, range.end());
if (start == limit) break;
out.write(line.substring(start - 1, end));
}
out.newLine();
}
}
private void cutFields(List<String> lines) throws IOException {
boolean first;
int limit, start, end;
for (String line : lines) {
if (!line.contains(inDelim)) {
if (!suppress) {
out.write(line);
out.newLine();
}
continue;
}
String[] fields = line.split(inDelim);
if (fields == null || fields.length == 0) {
out.newLine();
continue;
}
first = true;
limit = fields.length;
for (NumberRange range : list) {
start = Math.min(limit, range.start());
end = Math.min(limit, range.end());
if (start == limit) break;
for (int i = start - 1; i < end; i++) {
if (!first) {
out.write(outDelim);
}
first = false;
out.write(fields[i]);
}
}
out.newLine();
}
}
private void parseOptions() {
if (argByteRange.isSet()) {
mode = Mode.BYTE;
list = argByteRange.getValues();
}
if (argCharRange.isSet()) {
if (mode != null) {
error(err_multi_mode);
}
mode = Mode.CHAR;
list = argCharRange.getValues();
}
if (argFieldRange.isSet()) {
if (mode != null) {
error(err_multi_mode);
}
mode = Mode.FIELD;
list = argFieldRange.getValues();
}
if (mode == null) {
error(err_no_mode);
}
if (argInDelim.isSet()) {
if (mode != Mode.FIELD) {
error(err_delim);
}
inDelim = argInDelim.getValue();
} else {
inDelim = "\t";
}
if (argOutDelim.isSet()) {
if (mode != Mode.FIELD) {
error(err_delim);
}
outDelim = argOutDelim.getValue();
} else {
outDelim = inDelim;
}
if (argSuppress.isSet()) {
if (mode != Mode.FIELD) {
error(err_suppress);
}
suppress = true;
}
complement = argComplement.isSet();
if (argFiles.isSet()) {
files = argFiles.getValues();
} else {
files = new File[] {new File("-")};
}
}
private void error(String s) {
err.format(fmt_err, s);
exit(1);
}
}