This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new b352cc95b3 GROOVY-11742: posix commands should support variable
assignment (for for /wc and /sort)
b352cc95b3 is described below
commit b352cc95b32d35fc72968cfe276c34b89c8e9d29
Author: Paul King <[email protected]>
AuthorDate: Tue Aug 26 22:23:34 2025 +1000
GROOVY-11742: posix commands should support variable assignment (for for
/wc and /sort)
---
.../groovy/org/apache/groovy/groovysh/Main.groovy | 64 ++----
.../groovy/groovysh/jline/GroovyPosixCommands.java | 248 ++++++++++++++++++---
2 files changed, 229 insertions(+), 83 deletions(-)
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 84a8be0e53..cca9464657 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,8 @@ import static org.jline.jansi.AnsiRenderer.render
class Main {
private static final MessageSource messages = new MessageSource(Main)
public static final String INTERPRETER_MODE_PREFERENCE_KEY =
'interpreterMode'
- private static POSIX_FILE_CMDS = ['/wc', '/sort']
+// private static POSIX_CMDS = []
+ private static GROOVY_POSIX_CMDS = ['/ls', '/wc', '/sort', '/head',
'/tail', '/cat', '/grep']
@SuppressWarnings("resource")
protected static class ExtraConsoleCommands extends JlineCommandRegistry
implements CommandRegistry {
@@ -111,19 +112,20 @@ class Main {
'/cd' : new CommandMethods((Function) this::cd,
this::optDirCompleter),
'/date' : new CommandMethods((Function) this::date,
this::defaultCompleter),
'/echo' : new CommandMethods((Function) this::echo,
this::defaultCompleter),
- '/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)
]
- POSIX_FILE_CMDS.each { String cmd ->
- String orig = cmd[1..-1]
- usage[cmd] = adjustUsage(orig, cmd)
- posix.register(cmd, PosixCommands::"$orig")
- cmds.put(cmd, new CommandMethods((Function) this::posix,
this::optFileCompleter))
+ GROOVY_POSIX_CMDS.each { String cmd ->
+ String base = cmd[1..-1]
+ usage[cmd] = adjustUsage(base, cmd)
+ posix.register(cmd, PosixCommands::"$base")
+ cmds.put(cmd, new CommandMethods((Function)
this::posixCommand, this::optFileCompleter))
}
+// POSIX_CMDS.each { String cmd ->
+// String orig = cmd[1..-1]
+// usage[cmd] = adjustUsage(orig, cmd)
+// posix.register(cmd, PosixCommands::"$orig")
+// cmds.put(cmd, new CommandMethods((Function) this::posix,
this::optFileCompleter))
+// }
posix.register('cd', PosixCommands::cd)
posix.register('/cd', PosixCommands::cd)
posix.register('/pwd', PosixCommands::pwd)
@@ -176,33 +178,11 @@ class Main {
}
}
- private void ls(CommandInput input) {
+ private void posixCommand(CommandInput input) {
try {
- GroovyPosixCommands.ls(context(input), ['/ls', *input.args()]
as String[])
- } catch (Exception e) {
- saveException(e)
- }
- }
-
- private void grepcmd(CommandInput input) {
- try {
- GroovyPosixCommands.grep(context(input), ['/grep',
*input.xargs()] as Object[])
- } catch (Exception e) {
- saveException(e)
- }
- }
-
- private void headcmd(CommandInput input) {
- try {
- GroovyPosixCommands.head(context(input), ['/head',
*input.xargs()] as Object[])
- } catch (Exception e) {
- saveException(e)
- }
- }
-
- private void tailcmd(CommandInput input) {
- try {
- GroovyPosixCommands.tail(context(input), ['/tail',
*input.xargs()] as Object[])
+ String cmd = input.command()
+ String name = cmd[1..-1]
+ GroovyPosixCommands."$name"(context(input), [cmd,
*input.xargs()] as Object[])
} catch (Exception e) {
saveException(e)
}
@@ -214,14 +194,6 @@ class Main {
ctx
}
- private void cat(CommandInput input) {
- try {
- GroovyPosixCommands.cat(context(input), ['/cat',
*input.xargs()] as Object[])
- } catch (Exception e) {
- saveException(e)
- }
- }
-
private void date(CommandInput input) {
posix(adjustUsage('date', '/date'), input)
}
@@ -448,7 +420,7 @@ class Main {
if (!OSUtils.IS_WINDOWS) {
setSpecificHighlighter("/!",
SyntaxHighlighter.build(jnanorc, "SH-REPL"))
}
- addFileHighlight('/nano', '/less', '/slurp', '/load', '/save',
*POSIX_FILE_CMDS, '/cd', '/ls', '/cat', '/grep')
+ addFileHighlight('/nano', '/less', '/slurp', '/load', '/save',
*GROOVY_POSIX_CMDS, '/cd')
addFileHighlight('/classloader', null, ['-a', '--add'])
addExternalHighlighterRefresh(printer::refresh)
addExternalHighlighterRefresh(scriptEngine::refresh)
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 778d22bc9c..4dbd281a86 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
@@ -40,6 +40,7 @@ import org.jline.utils.OSUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -121,10 +122,117 @@ public class GroovyPosixCommands extends PosixCommands {
}
}
+ public static void wc(Context context, Object[] argv) throws Exception {
+ final String[] usage = {
+ "/wc - word, line, character, and byte count",
+ "Usage: /wc [OPTIONS] [FILES]",
+ " -? --help Show help",
+ " -l --lines Print line counts",
+ " -c --bytes Print byte counts",
+ " -m --chars Print character counts",
+ " -w --words Print word counts",
+ " --total=WHEN Print total counts,
WHEN=auto|always|never|only",
+ };
+ Options opt = parseOptions(context, usage, argv);
+
+ List<String> args = opt.args();
+ if (args.isEmpty()) {
+ args = Collections.singletonList("-");
+ }
+ List<NamedInputStream> sources = getSources(context, argv, args);
+
+ boolean showLines = opt.isSet("lines");
+ boolean showWords = opt.isSet("words");
+ boolean showChars = opt.isSet("chars");
+ boolean showBytes = opt.isSet("bytes");
+ boolean only = false;
+ boolean total = sources.size() > 1;
+ String totalOpt = opt.isSet("total") ? opt.get("total") : "auto";
+ switch (totalOpt) {
+ case "always":
+ case "yes":
+ case "force":
+ total = true;
+ break;
+ case "never":
+ case "no":
+ case "none":
+ total = false;
+ break;
+ case "only":
+ only = true;
+ break;
+ case "auto":
+ case "tty":
+ case "if-tty":
+ total = context.isTty();
+ break;
+ default:
+ throw new IllegalArgumentException("invalid argument '" +
totalOpt + "' for '--total'");
+ }
+
+ // If no options specified, show all
+ if (!showLines && !showWords && !showChars && !showBytes) {
+ showLines = showWords = showBytes = true;
+ }
+
+ long totalLines = 0, totalWords = 0, totalChars = 0, totalBytes = 0;
+
+ for (NamedInputStream source : sources) {
+ long lines = 0, words = 0, chars = 0, bytes = 0;
+
+ try (BufferedReader reader = new BufferedReader(new
InputStreamReader(source.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ lines++;
+ chars += line.length() + 1; // +1 for newline
+ bytes += line.getBytes().length + 1; // +1 for newline
+
+ // Count words
+ String[] wordArray = line.trim().split("\\s+");
+ if (wordArray.length == 1 && wordArray[0].isEmpty()) {
+ // Empty line
+ } else {
+ words += wordArray.length;
+ }
+ }
+ }
+
+ totalLines += lines;
+ totalWords += words;
+ totalChars += chars;
+ totalBytes += bytes;
+
+ if (only) continue;
+ // Print results for this file
+ StringBuilder result = new StringBuilder();
+ if (showLines) result.append(String.format("%8d", lines));
+ if (showWords) result.append(String.format("%8d", words));
+ if (showChars) result.append(String.format("%8d", chars));
+ if (showBytes) result.append(String.format("%8d", bytes));
+ result.append(" ").append(source.getName());
+
+ context.out().println(result);
+ context.out().flush();
+ }
+
+ // Print totals if multiple files
+ if (total) {
+ StringBuilder result = new StringBuilder();
+ if (showLines) result.append(String.format("%8d", totalLines));
+ if (showWords) result.append(String.format("%8d", totalWords));
+ if (showChars) result.append(String.format("%8d", totalChars));
+ if (showBytes) result.append(String.format("%8d", totalBytes));
+ result.append(" total");
+
+ context.out().println(result);
+ }
+ }
+
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 | -q | -v] [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",
@@ -134,7 +242,11 @@ public class GroovyPosixCommands extends PosixCommands {
Options opt = parseOptions(context, usage, argv);
if (opt.isSet("lines") && opt.isSet("bytes")) {
- throw new IllegalArgumentException("usage: /head [-n # | -c # | -q
| -v] [file|variable ...]");
+ throw new IllegalArgumentException("usage: /head [-n # | -c #] [-q
| -v] [file|variable ...]");
+ }
+
+ if (opt.isSet("quiet") && opt.isSet("verbose")) {
+ throw new IllegalArgumentException("usage: /head [-n # | -c #] [-q
| -v] [file|variable ...]");
}
int nbLines = Integer.MAX_VALUE;
@@ -195,7 +307,7 @@ public class GroovyPosixCommands extends PosixCommands {
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 ...]",
+ "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",
@@ -205,7 +317,11 @@ public class GroovyPosixCommands extends PosixCommands {
Options opt = parseOptions(context, usage, argv);
if (opt.isSet("lines") && opt.isSet("bytes")) {
- throw new IllegalArgumentException("usage: /tail [-c # | -n # | -q
| -v] [file|variable ...]");
+ throw new IllegalArgumentException("usage: /tail [-c # | -n #] [-q
| -v] [file|variable ...]");
+ }
+
+ if (opt.isSet("quiet") && opt.isSet("verbose")) {
+ throw new IllegalArgumentException("usage: /tail [-c # | -n #] [-q
| -v] [file|variable ...]");
}
int lines = opt.isSet("lines") ? opt.getNumber("lines") : 10;
@@ -259,15 +375,7 @@ public class GroovyPosixCommands extends PosixCommands {
}
}
- private static InputStream newInputStream(Path p) {
- try {
- return Files.newInputStream(p);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public static void ls(Context context, String[] argv) throws Exception {
+ public static void ls(Context context, Object[] argv) throws Exception {
final String[] usage = {
"/ls - list files",
"Usage: /ls [OPTIONS] [PATTERNS...]",
@@ -891,26 +999,51 @@ public class GroovyPosixCommands extends PosixCommands {
}
}
- private static List<NamedInputStream> getSources(Context context, Object[]
argv, List<String> args) {
- List<NamedInputStream> sources = new ArrayList<>();
- for (String arg : args) {
- if ("-".equals(arg)) {
- sources.add(new NamedInputStream(context.in(), "(standard
input)"));
- } else if (arg.startsWith("[Ljava.lang.String;@")) {
- sources.add(new NamedInputStream(variableInputStream(argv,
arg), arg));
- } else {
- sources.addAll(maybeExpandGlob(context, arg)
- .map(gp -> new NamedInputStream(newInputStream(gp),
gp.toString()))
- .collect(Collectors.toList()));
+ public static void sort(Context context, Object[] argv) throws Exception {
+ final String[] usage = {
+ "/sort - writes sorted standard input to standard output.",
+ "Usage: /sort [OPTIONS] [FILES]",
+ " -? --help show help",
+ " -f --ignore-case fold lower case to upper case
characters",
+ " -r --reverse reverse the result of comparisons",
+ " -u --unique output only the first of an equal
run",
+ " -t --field-separator=SEP use SEP instead of non-blank to
blank transition",
+ " -b --ignore-leading-blanks ignore leading blancks",
+ " --numeric-sort compare according to string
numerical value",
+ " -k --key=KEY fields to use for sorting
separated by whitespaces"
+ };
+
+ Options opt = parseOptions(context, usage, argv);
+
+ List<String> args = opt.args();
+ if (args.isEmpty()) {
+ args = Collections.singletonList("-");
+ }
+ List<NamedInputStream> sources = getSources(context, argv, args);
+ List<String> lines = new ArrayList<>();
+ for (NamedInputStream s : sources) {
+ try (BufferedReader reader = new BufferedReader(new
InputStreamReader(s.getInputStream()))) {
+ readLines(reader, lines);
}
}
- return sources;
- }
- private static ByteArrayInputStream variableInputStream(Object[] argv,
String arg) {
- String[] found = (String[]) Arrays.stream(argv).filter(v ->
v.toString().equals(arg)).findFirst().get();
- ByteArrayInputStream inputStream = new
ByteArrayInputStream(ArrayGroovyMethods.join(found,
"\n").getBytes(StandardCharsets.UTF_8));
- return inputStream;
+ String separator = opt.get("field-separator");
+ boolean caseInsensitive = opt.isSet("ignore-case");
+ boolean reverse = opt.isSet("reverse");
+ boolean ignoreBlanks = opt.isSet("ignore-leading-blanks");
+ boolean numeric = opt.isSet("numeric-sort");
+ boolean unique = opt.isSet("unique");
+ List<String> sortFields = opt.getList("key");
+
+ char sep = (separator == null || separator.length() == 0) ? '\0' :
separator.charAt(0);
+ lines.sort(new SortComparator(caseInsensitive, reverse, ignoreBlanks,
numeric, sep, sortFields));
+ String last = null;
+ for (String s : lines) {
+ if (!unique || last == null || !s.equals(last)) {
+ context.out().println(s);
+ }
+ last = s;
+ }
}
public static void less(Context context, String[] argv) throws Exception {
@@ -953,34 +1086,68 @@ public class GroovyPosixCommands extends PosixCommands {
less.run(sources);
}
- private static class NamedInputStream {
- private final InputStream inputStream;
+ private static class NamedInputStream implements Closeable {
+ private InputStream inputStream;
private final Path path;
private final String name;
+ private final boolean close;
- public NamedInputStream(InputStream inputStream, String name) {
+ public NamedInputStream(InputStream inputStream, String name, boolean
close) {
this.inputStream = inputStream;
this.path = null;
this.name = name;
+ this.close = close;
}
+ public NamedInputStream(InputStream inputStream, String name) {
+ this(inputStream, name, true);
+ }
public NamedInputStream(Path path, String name) {
this.inputStream = null;
this.path = path;
this.name = name;
+ this.close = false;
}
public InputStream getInputStream() throws IOException {
- if (inputStream != null) {
- return inputStream;
- } else {
- return path.toUri().toURL().openStream();
+ if (inputStream == null) {
+ inputStream = path.toUri().toURL().openStream();
}
+ return inputStream;
}
public String getName() {
return name;
}
+
+ @Override
+ public void close() throws IOException {
+ if (inputStream != null && close) {
+ inputStream.close();
+ }
+ }
+ }
+
+ private static List<NamedInputStream> getSources(Context context, Object[]
argv, List<String> args) {
+ List<NamedInputStream> sources = new ArrayList<>();
+ for (String arg : args) {
+ if ("-".equals(arg)) {
+ sources.add(new NamedInputStream(context.in(), "(standard
input)", false));
+ } else if (arg.startsWith("[Ljava.lang.String;@")) {
+ sources.add(new NamedInputStream(variableInputStream(argv,
arg), arg));
+ } else {
+ sources.addAll(maybeExpandGlob(context, arg)
+ .map(p -> new NamedInputStream(p, p.toString()))
+ .collect(Collectors.toList()));
+ }
+ }
+ return sources;
+ }
+
+ private static ByteArrayInputStream variableInputStream(Object[] argv,
String arg) {
+ String[] found = (String[]) Arrays.stream(argv).filter(v ->
v.toString().equals(arg)).findFirst().get();
+ ByteArrayInputStream inputStream = new
ByteArrayInputStream(ArrayGroovyMethods.join(found,
"\n").getBytes(StandardCharsets.UTF_8));
+ return inputStream;
}
private static LinkOption[] getLinkOptions(boolean followLinks) {
@@ -1022,6 +1189,13 @@ public class GroovyPosixCommands extends PosixCommands {
return perms;
}
+ private static void readLines(BufferedReader reader, List<String> lines)
throws IOException {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ lines.add(line);
+ }
+ }
+
private static Stream<Path> maybeExpandGlob(Context context, String s) {
if (s.contains("*") || s.contains("?")) {
return expandGlob(context, s).stream();