GROOVY-11742: posix commands should support variable assignment (amendments for /tail)
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
index 3d07e07..84a8be0 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
@@ -82,7 +82,7 @@
class Main {
private static final MessageSource messages = new MessageSource(Main)
public static final String INTERPRETER_MODE_PREFERENCE_KEY = 'interpreterMode'
- private static POSIX_FILE_CMDS = ['/tail', '/wc', '/sort']
+ private static POSIX_FILE_CMDS = ['/wc', '/sort']
@SuppressWarnings("resource")
protected static class ExtraConsoleCommands extends JlineCommandRegistry implements CommandRegistry {
@@ -114,6 +114,7 @@
'/ls' : new CommandMethods((Function) this::ls, this::optFileCompleter),
'/grep' : new CommandMethods((Function) this::grepcmd, this::optFileCompleter),
'/head' : new CommandMethods((Function) this::headcmd, this::optFileCompleter),
+ '/tail' : new CommandMethods((Function) this::tailcmd, this::optFileCompleter),
'/cat' : new CommandMethods((Function) this::cat, this::optFileCompleter),
"/!" : new CommandMethods((Function) this::shell, this::defaultCompleter)
]
@@ -199,6 +200,14 @@
}
}
+ private void tailcmd(CommandInput input) {
+ try {
+ GroovyPosixCommands.tail(context(input), ['/tail', *input.xargs()] as Object[])
+ } catch (Exception e) {
+ saveException(e)
+ }
+ }
+
private GroovyPosixContext context(CommandInput input) {
GroovyPosixContext ctx = new GroovyPosixContext(input.in(), input.out(), input.err(),
posix.context.currentDir(), input.terminal(), scriptEngine::get)
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyPosixCommands.java b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyPosixCommands.java
index 5cde0fc..dcf56c2 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyPosixCommands.java
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyPosixCommands.java
@@ -31,6 +31,7 @@
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -115,15 +116,17 @@
public static void head(Context context, Object[] argv) throws Exception {
final String[] usage = {
"/head - display first lines of files or variables",
- "Usage: /head [-n lines | -c bytes] [file|variable ...]",
+ "Usage: /head [-n lines | -c bytes | -q | -v] [file|variable ...]",
" -? --help Show help",
" -n --lines=LINES Print line counts",
" -c --bytes=BYTES Print byte counts",
+ " -q --quiet Never output filename headers",
+ " -v --verbose Always output filename headers",
};
Options opt = parseOptions(context, usage, argv);
if (opt.isSet("lines") && opt.isSet("bytes")) {
- throw new IllegalArgumentException("usage: head [-n # | -c #] [file ...]");
+ throw new IllegalArgumentException("usage: /head [-n # | -c # | -q | -v] [file|variable ...]");
}
int nbLines = Integer.MAX_VALUE;
@@ -144,35 +147,110 @@
boolean first = true;
List<NamedInputStream> sources = getSources(context, argv, args);
for (NamedInputStream nis : sources) {
- if (!first && args.size() > 1) {
- context.out().println();
+ boolean filenameHeader = sources.size() > 1;
+ if (opt.isSet("verbose")) {
+ filenameHeader = true;
+ } else if (opt.isSet("quiet")) {
+ filenameHeader = false;
}
- if (args.size() > 1) {
+ if (filenameHeader) {
+ if (!first) {
+ context.out().println();
+ }
context.out().println("==> " + nis.getName() + " <==");
}
-
- InputStream is = nis.getInputStream();
- if (nbLines != Integer.MAX_VALUE) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
- String line;
- int count = 0;
- while ((line = reader.readLine()) != null && count < nbLines) {
- context.out().println(line);
- count++;
- }
- }
- } else {
- byte[] buffer = new byte[nbBytes];
- int bytesRead = is.read(buffer);
- if (bytesRead > 0) {
- context.out().write(buffer, 0, bytesRead);
- }
- is.close();
- }
+ doHead(context, nis.getInputStream(), nbLines, nbBytes);
first = false;
}
}
+ private static void doHead(Context context, InputStream is, final int nbLines, final int nbBytes) throws IOException {
+ if (nbLines != Integer.MAX_VALUE) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+ String line;
+ int count = 0;
+ while ((line = reader.readLine()) != null && count < nbLines) {
+ context.out().println(line);
+ count++;
+ }
+ }
+ } else {
+ byte[] buffer = new byte[nbBytes];
+ int bytesRead = is.read(buffer);
+ if (bytesRead > 0) {
+ context.out().write(buffer, 0, bytesRead);
+ }
+ is.close();
+ }
+ }
+
+ public static void tail(Context context, Object[] argv) throws Exception {
+ final String[] usage = {
+ "/tail - display last lines of files or variables",
+ "Usage: /tail [-n lines | -c bytes | -q | -v] [file|variable ...]",
+ " -? --help Show help",
+ " -n --lines=LINES Number of lines to print",
+ " -c --bytes=BYTES Number of bytes to print",
+ " -q --quiet Never output filename headers",
+ " -v --verbose Always output filename headers",
+ };
+ Options opt = parseOptions(context, usage, argv);
+
+ if (opt.isSet("lines") && opt.isSet("bytes")) {
+ throw new IllegalArgumentException("usage: /tail [-c # | -n # | -q | -v] [file|variable ...]");
+ }
+
+ int lines = opt.isSet("lines") ? opt.getNumber("lines") : 10;
+ int bytes = opt.isSet("bytes") ? opt.getNumber("bytes") : -1;
+
+ List<String> args = opt.args();
+ if (args.isEmpty()) {
+ args = Collections.singletonList("-");
+ }
+
+ List<NamedInputStream> sources = getSources(context, argv, args);
+ boolean filenameHeader = sources.size() > 1;
+ if (opt.isSet("verbose")) {
+ filenameHeader = true;
+ } else if (opt.isSet("quiet")) {
+ filenameHeader = false;
+ }
+ for (NamedInputStream nis : sources) {
+ if (filenameHeader) {
+ context.out().println("==> " + nis.getName() + " <==");
+ }
+ tailInputStream(context, nis.getInputStream(), lines, bytes);
+ }
+ }
+
+ private static void tailInputStream(Context context, InputStream is, int lines, int bytes) throws IOException {
+ if (bytes > 0) {
+ // Read all and keep last bytes
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[8192];
+ int n;
+ while ((n = is.read(buffer)) != -1) {
+ baos.write(buffer, 0, n);
+ }
+ byte[] data = baos.toByteArray();
+ int start = Math.max(0, data.length - bytes);
+ context.out().write(data, start, data.length - start);
+ } else {
+ // Read all and keep last lines
+ List<String> allLines = new ArrayList<>();
+ try (BufferedReader reader = new BufferedReader(new java.io.InputStreamReader(is))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ allLines.add(line);
+ }
+ }
+ int start = Math.max(0, allLines.size() - lines);
+ for (int i = start; i < allLines.size(); i++) {
+ context.out().println(allLines.get(i));
+ }
+ }
+ }
+
private static InputStream newInputStream(Path p) {
try {
return Files.newInputStream(p);