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 <pa...@asert.com.au>
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();

Reply via email to