// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.builtins;

import java.util.HashSet;
import org.jline.utils.OSUtils;
import org.jline.utils.StyleResolver;
import java.util.Arrays;
import java.util.function.IntBinaryOperator;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedCharSequence;
import org.jline.utils.AttributedString;
import java.io.PrintStream;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.function.BiConsumer;
import java.util.TreeMap;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.time.format.DateTimeFormatter;
import java.time.ZoneId;
import java.time.Instant;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.EnumSet;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import java.nio.file.attribute.FileTime;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.Map;
import org.jline.utils.AttributedStringBuilder;
import java.util.regex.Pattern;
import java.io.RandomAccessFile;
import java.io.ByteArrayOutputStream;
import java.util.stream.Stream;
import java.nio.file.PathMatcher;
import java.util.function.Predicate;
import java.util.Objects;
import java.nio.file.FileVisitOption;
import java.nio.file.FileSystems;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.time.LocalDateTime;
import org.jline.utils.InfoCmp;
import java.util.concurrent.Executors;
import java.util.Collection;
import java.util.ArrayList;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.io.InputStream;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Iterator;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.util.List;
import java.nio.file.LinkOption;

public class PosixCommands
{
    public static final String DEFAULT_LS_COLORS = "dr=1;91:ex=1;92:sl=1;96:ot=34;43";
    public static final String DEFAULT_GREP_COLORS = "mt=1;31:fn=35:ln=32:se=36";
    private static final LinkOption[] NO_FOLLOW_OPTIONS;
    private static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS;
    private static final LinkOption[] EMPTY_LINK_OPTIONS;
    
    public static void cd(final Context context, final String[] argv) throws Exception {
        cd(context, argv, null);
    }
    
    public static void cd(final Context context, final String[] argv, final Consumer<Path> directoryChanger) throws Exception {
        final String[] usage = { "cd - change directory", "Usage: cd [OPTIONS] [DIRECTORY]", "  -? --help                show help", "  -P                       use physical directory structure", "  -L                       follow symbolic links (default)" };
        final Options opt = parseOptions(context, usage, argv);
        if (opt.args().size() != 1) {
            throw new IllegalArgumentException("usage: cd DIRECTORY");
        }
        final Path cwd = context.currentDir();
        Path newDir;
        if (opt.args().isEmpty()) {
            final String home = System.getProperty("user.home");
            if (home != null) {
                newDir = Paths.get(home, new String[0]);
            }
            else {
                newDir = cwd;
            }
        }
        else {
            final String target = opt.args().get(0);
            if ("-".equals(target)) {
                newDir = cwd;
            }
            else {
                newDir = cwd.resolve(target);
            }
        }
        if (opt.isSet("P")) {
            newDir = newDir.toRealPath(new LinkOption[0]);
        }
        else {
            newDir = newDir.toAbsolutePath().normalize();
        }
        if (!Files.exists(newDir, new LinkOption[0])) {
            throw new IOException("cd: no such file or directory: " + opt.args().get(0));
        }
        if (!Files.isDirectory(newDir, new LinkOption[0])) {
            throw new IOException("cd: not a directory: " + opt.args().get(0));
        }
        if (directoryChanger != null) {
            directoryChanger.accept(newDir);
        }
    }
    
    public static void pwd(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "pwd - print working directory", "Usage: pwd [OPTIONS]", "  -? --help                show help" };
        final Options opt = parseOptions(context, usage, argv);
        if (!opt.args().isEmpty()) {
            throw new IllegalArgumentException("usage: pwd");
        }
        context.out().println(context.currentDir());
    }
    
    public static void echo(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "echo - display text", "Usage: echo [OPTIONS] [ARGUMENTS]", "  -? --help                show help", "  -n                       no trailing new line" };
        final Options opt = parseOptions(context, usage, argv);
        final List<String> args = opt.args();
        final StringBuilder buf = new StringBuilder();
        if (args != null) {
            for (final String arg : args) {
                if (buf.length() > 0) {
                    buf.append(' ');
                }
                for (int i = 0; i < arg.length(); ++i) {
                    int c = arg.charAt(i);
                    if (c == 92) {
                        c = ((i < arg.length() - 1) ? arg.charAt(++i) : '\\');
                        switch (c) {
                            case 97: {
                                buf.append('\u0007');
                                break;
                            }
                            case 110: {
                                buf.append('\n');
                                break;
                            }
                            case 116: {
                                buf.append('\t');
                                break;
                            }
                            case 114: {
                                buf.append('\r');
                                break;
                            }
                            case 92: {
                                buf.append('\\');
                                break;
                            }
                            case 48:
                            case 49:
                            case 50:
                            case 51:
                            case 52:
                            case 53:
                            case 54:
                            case 55:
                            case 56:
                            case 57: {
                                int ch = 0;
                                for (int j = 0; j < 3; ++j) {
                                    c = ((i < arg.length() - 1) ? arg.charAt(++i) : -1);
                                    if (c >= 0) {
                                        ch = ch * 8 + (c - 48);
                                    }
                                }
                                buf.append((char)ch);
                                break;
                            }
                            case 117: {
                                int ch = 0;
                                for (int j = 0; j < 4; ++j) {
                                    c = ((i < arg.length() - 1) ? arg.charAt(++i) : -1);
                                    if (c >= 0) {
                                        if (c >= 65 && c <= 90) {
                                            ch = ch * 16 + (c - 65 + 10);
                                        }
                                        else if (c >= 97 && c <= 122) {
                                            ch = ch * 16 + (c - 97 + 10);
                                        }
                                        else {
                                            if (c < 48 || c > 57) {
                                                break;
                                            }
                                            ch = ch * 16 + (c - 48);
                                        }
                                    }
                                }
                                buf.append((char)ch);
                                break;
                            }
                            default: {
                                buf.append((char)c);
                                break;
                            }
                        }
                    }
                    else {
                        buf.append((char)c);
                    }
                }
            }
        }
        if (opt.isSet("n")) {
            context.out().print(buf);
        }
        else {
            context.out().println(buf);
        }
    }
    
    public static void echo(final Context context, final Object[] argv) throws Exception {
        final String[] stringArgv = new String[argv.length];
        for (int i = 0; i < argv.length; ++i) {
            stringArgv[i] = ((argv[i] != null) ? argv[i].toString() : "");
        }
        echo(context, stringArgv);
    }
    
    public static void cat(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "cat - concatenate and print FILES", "Usage: cat [OPTIONS] [FILES]", "  -? --help                show help", "  -n                       number the output lines, starting at 1" };
        final Options opt = parseOptions(context, usage, argv);
        List<String> args = opt.args();
        if (args.isEmpty()) {
            args = Collections.singletonList("-");
        }
        final Path cwd = context.currentDir();
        for (final String arg : args) {
            InputStream is;
            if ("-".equals(arg)) {
                is = context.in();
            }
            else {
                is = cwd.toUri().resolve(arg).toURL().openStream();
            }
            cat(context, new BufferedReader(new InputStreamReader(is)), opt.isSet("n"));
        }
    }
    
    private static void cat(final Context context, final BufferedReader reader, final boolean numbered) throws IOException {
        int lineno = 1;
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                if (numbered) {
                    context.out().printf("%6d\t%s%n", lineno++, line);
                }
                else {
                    context.out().println(line);
                }
            }
        }
        finally {
            reader.close();
        }
    }
    
    public static void date(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "date - display date", "Usage: date [OPTIONS] [+FORMAT]", "  -? --help                    Show help", "  -u --utc                     Use UTC timezone", "  -r --reference=SECONDS       Print the date represented by 'seconds' since January 1, 1970", "  -d --date=STRING             Display time described by STRING", "  -f --file=DATEFILE           Like --date once for each line of DATEFILE", "  -I --iso-8601[=TIMESPEC]     Output date/time in ISO 8601 format", "  -R --rfc-2822                Output date and time in RFC 2822 format", "     --rfc-3339=TIMESPEC       Output date and time in RFC 3339 format" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        Date input = new Date();
        String output = null;
        final boolean useUtc = opt.isSet("utc");
        if (opt.isSet("reference")) {
            final long seconds = Long.parseLong(opt.get("reference"));
            input = new Date(seconds * 1000L);
        }
        if (opt.isSet("date")) {
            final String dateStr = opt.get("date");
            try {
                final SimpleDateFormat[] formats = { new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("MM/dd/yyyy"), new SimpleDateFormat("dd-MM-yyyy"), new SimpleDateFormat("yyyy/MM/dd") };
                boolean parsed = false;
                final SimpleDateFormat[] array = formats;
                final int length = array.length;
                int i = 0;
                while (i < length) {
                    final SimpleDateFormat format = array[i];
                    try {
                        input = format.parse(dateStr);
                        parsed = true;
                    }
                    catch (final Exception ex) {
                        ++i;
                        continue;
                    }
                    break;
                }
                if (!parsed) {
                    throw new IllegalArgumentException("Unable to parse date: " + dateStr);
                }
            }
            catch (final Exception e) {
                throw new IllegalArgumentException("Invalid date string: " + dateStr);
            }
        }
        if (opt.isSet("iso-8601")) {
            final String timespec = opt.get("iso-8601");
            if (timespec == null || "date".equals(timespec)) {
                output = "%Y-%m-%d";
            }
            else if ("hours".equals(timespec)) {
                output = "%Y-%m-%dT%H%z";
            }
            else if ("minutes".equals(timespec)) {
                output = "%Y-%m-%dT%H:%M%z";
            }
            else if ("seconds".equals(timespec)) {
                output = "%Y-%m-%dT%H:%M:%S%z";
            }
            else if ("ns".equals(timespec)) {
                output = "%Y-%m-%dT%H:%M:%S,%N%z";
            }
        }
        if (opt.isSet("rfc-2822")) {
            output = "%a, %d %b %Y %H:%M:%S %z";
        }
        if (opt.isSet("rfc-3339")) {
            final String timespec = opt.get("rfc-3339");
            if ("date".equals(timespec)) {
                output = "%Y-%m-%d";
            }
            else if ("seconds".equals(timespec)) {
                output = "%Y-%m-%d %H:%M:%S%z";
            }
            else if ("ns".equals(timespec)) {
                output = "%Y-%m-%d %H:%M:%S.%N%z";
            }
        }
        final List<String> args = opt.args();
        if (!args.isEmpty()) {
            final String arg = args.get(0);
            if (arg.startsWith("+")) {
                output = arg.substring(1);
            }
        }
        if (output == null) {
            output = "%c";
        }
        final SimpleDateFormat formatter = new SimpleDateFormat(toJavaDateFormat(output));
        if (useUtc) {
            formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        }
        context.out().println(formatter.format(input));
    }
    
    private static String toJavaDateFormat(final String format) {
        final StringBuilder sb = new StringBuilder();
        boolean quote = false;
        for (int i = 0; i < format.length(); ++i) {
            char c = format.charAt(i);
            if (c == '%') {
                if (i + 1 < format.length()) {
                    if (quote) {
                        sb.append('\'');
                        quote = false;
                    }
                    c = format.charAt(++i);
                    switch (c) {
                        case '+':
                        case 'A': {
                            sb.append("MMM EEE d HH:mm:ss yyyy");
                            break;
                        }
                        case 'a': {
                            sb.append("EEE");
                            break;
                        }
                        case 'B': {
                            sb.append("MMMMMMM");
                            break;
                        }
                        case 'b': {
                            sb.append("MMM");
                            break;
                        }
                        case 'C': {
                            sb.append("yy");
                            break;
                        }
                        case 'c': {
                            sb.append("MMM EEE d HH:mm:ss yyyy");
                            break;
                        }
                        case 'D': {
                            sb.append("MM/dd/yy");
                            break;
                        }
                        case 'd': {
                            sb.append("dd");
                            break;
                        }
                        case 'e': {
                            sb.append("dd");
                            break;
                        }
                        case 'F': {
                            sb.append("yyyy-MM-dd");
                            break;
                        }
                        case 'G': {
                            sb.append("YYYY");
                            break;
                        }
                        case 'g': {
                            sb.append("YY");
                            break;
                        }
                        case 'H': {
                            sb.append("HH");
                            break;
                        }
                        case 'h': {
                            sb.append("MMM");
                            break;
                        }
                        case 'I': {
                            sb.append("hh");
                            break;
                        }
                        case 'j': {
                            sb.append("DDD");
                            break;
                        }
                        case 'k': {
                            sb.append("HH");
                            break;
                        }
                        case 'l': {
                            sb.append("hh");
                            break;
                        }
                        case 'M': {
                            sb.append("mm");
                            break;
                        }
                        case 'm': {
                            sb.append("MM");
                            break;
                        }
                        case 'N': {
                            sb.append("S");
                            break;
                        }
                        case 'n': {
                            sb.append("\n");
                            break;
                        }
                        case 'P': {
                            sb.append("aa");
                            break;
                        }
                        case 'p': {
                            sb.append("aa");
                            break;
                        }
                        case 'r': {
                            sb.append("hh:mm:ss aa");
                            break;
                        }
                        case 'R': {
                            sb.append("HH:mm");
                            break;
                        }
                        case 'S': {
                            sb.append("ss");
                            break;
                        }
                        case 's': {
                            sb.append("S");
                            break;
                        }
                        case 'T': {
                            sb.append("HH:mm:ss");
                            break;
                        }
                        case 't': {
                            sb.append("\t");
                            break;
                        }
                        case 'U': {
                            sb.append("w");
                            break;
                        }
                        case 'u': {
                            sb.append("u");
                            break;
                        }
                        case 'V': {
                            sb.append("W");
                            break;
                        }
                        case 'v': {
                            sb.append("dd-MMM-yyyy");
                            break;
                        }
                        case 'W': {
                            sb.append("w");
                            break;
                        }
                        case 'w': {
                            sb.append("u");
                            break;
                        }
                        case 'X': {
                            sb.append("HH:mm:ss");
                            break;
                        }
                        case 'x': {
                            sb.append("MM/dd/yy");
                            break;
                        }
                        case 'Y': {
                            sb.append("yyyy");
                            break;
                        }
                        case 'y': {
                            sb.append("yy");
                            break;
                        }
                        case 'Z': {
                            sb.append("z");
                            break;
                        }
                        case 'z': {
                            sb.append("X");
                            break;
                        }
                        case '%': {
                            sb.append("%");
                            break;
                        }
                    }
                }
                else {
                    if (!quote) {
                        sb.append('\'');
                    }
                    sb.append(c);
                    sb.append('\'');
                }
            }
            else {
                if (((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) && !quote) {
                    sb.append('\'');
                    quote = true;
                }
                sb.append(c);
            }
        }
        return sb.toString();
    }
    
    public static void sleep(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "sleep - suspend execution for an interval of time", "Usage: sleep seconds", "  -? --help                    show help" };
        final Options opt = parseOptions(context, usage, argv);
        final List<String> args = opt.args();
        if (args.size() != 1) {
            throw new IllegalArgumentException("usage: sleep seconds");
        }
        final int s = Integer.parseInt(args.get(0));
        Thread.sleep(s * 1000L);
    }
    
    public static void watch(final Context context, final String[] argv) throws Exception {
        watch(context, argv, null);
    }
    
    public static void watch(final Context context, final String[] argv, final CommandExecutor executor) throws Exception {
        final String[] usage = { "watch - watches & refreshes the output of a command", "Usage: watch [OPTIONS] COMMAND", "  -? --help                    Show help", "  -n --interval=SECONDS        Interval between executions of the command in seconds", "  -a --append                  The output should be appended but not clear the console" };
        final Options opt = parseOptions(context, usage, argv);
        final List<String> args = opt.args();
        if (args.isEmpty()) {
            throw new IllegalArgumentException("usage: watch COMMAND");
        }
        int intervalValue = 1;
        if (opt.isSet("interval")) {
            intervalValue = opt.getNumber("interval");
            if (intervalValue < 1) {
                intervalValue = 1;
            }
        }
        final int interval = intervalValue;
        final boolean append = opt.isSet("append");
        final String command = String.join(" ", args);
        final List<String> finalArgs = new ArrayList<String>(args);
        final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        try {
            final Runnable task = () -> {
                try {
                    if (!append && context.isTty()) {
                        context.terminal().puts(InfoCmp.Capability.clear_screen, new Object[0]);
                        context.terminal().flush();
                    }
                    else if (!append) {
                        context.out().println();
                    }
                    context.out().println("Every " + interval + "s: " + command + "    " + LocalDateTime.now());
                    context.out().println();
                    if (executor != null) {
                        try {
                            final String output = executor.execute(finalArgs);
                            context.out().print(output);
                        }
                        catch (final Exception e) {
                            context.err().println("Command execution failed: " + e.getMessage());
                        }
                    }
                    else {
                        try {
                            final Process process = new ProcessBuilder(finalArgs).start();
                            new BufferedReader(new InputStreamReader(process.getInputStream()));
                            final BufferedReader bufferedReader;
                            try (final BufferedReader reader = bufferedReader) {
                                while (true) {
                                    final String line = reader.readLine();
                                    final Object o;
                                    if (o != null) {
                                        context.out().println(line);
                                    }
                                    else {
                                        break;
                                    }
                                }
                            }
                            process.waitFor();
                        }
                        catch (final Exception e2) {
                            context.out().println("Command: " + command);
                            context.out().println("(Command execution requires shell integration - use gogo implementation for full functionality)");
                        }
                    }
                    context.out().flush();
                }
                catch (final Exception e3) {
                    context.err().println("Error executing command: " + e3.getMessage());
                }
                return;
            };
            executorService.scheduleAtFixedRate(task, 0L, interval, TimeUnit.SECONDS);
            if (context.isTty()) {
                context.out().println("Press any key to stop...");
                context.in().read();
            }
            else {
                Thread.sleep(10000L);
            }
        }
        finally {
            executorService.shutdownNow();
        }
    }
    
    public static void ttop(final Context context, final String[] argv) throws Exception {
        TTop.ttop(context.terminal(), context.out(), context.err(), argv);
    }
    
    public static void nano(final Context context, final String[] argv) throws Exception {
        final Options opt = parseOptions(context, Nano.usage(), argv);
        final Nano nano = new Nano(context.terminal(), context.currentDir(), opt);
        nano.open(opt.args());
        nano.run();
    }
    
    public static void less(final Context context, final String[] argv) throws Exception {
        final Options opt = parseOptions(context, Less.usage(), argv);
        final List<Source> sources = new ArrayList<Source>();
        if (opt.args().isEmpty()) {
            opt.args().add("-");
        }
        for (final String arg : opt.args()) {
            if ("-".equals(arg)) {
                sources.add(new Source.StdInSource(context.in()));
            }
            else if (arg.contains("*") || arg.contains("?")) {
                final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + arg);
                try (final Stream<Path> pathStream = Files.walk(context.currentDir(), new FileVisitOption[0])) {
                    final Stream<Path> stream = pathStream;
                    final PathMatcher obj = pathMatcher;
                    Objects.requireNonNull(obj);
                    stream.filter(obj::matches).forEach(p -> {
                        try {
                            sources.add(new Source.URLSource(p.toUri().toURL(), p.toString()));
                        }
                        catch (final Exception e2) {
                            throw new RuntimeException(e2);
                        }
                        return;
                    });
                    if (pathStream == null) {
                        continue;
                    }
                }
            }
            else {
                try {
                    final Path path = context.currentDir().resolve(arg);
                    sources.add(new Source.URLSource(path.toUri().toURL(), arg));
                }
                catch (final Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        if (!context.isTty()) {
            for (final Source source : sources) {
                try (final BufferedReader reader = new BufferedReader(new InputStreamReader(source.read()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        context.out().println(line);
                    }
                }
            }
            return;
        }
        final Less less = new Less(context.terminal(), context.currentDir(), opt);
        less.run(sources);
    }
    
    public static void clear(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "clear - clear screen", "Usage: clear [OPTIONS]", "  -? --help                    Show help" };
        final Options opt = parseOptions(context, usage, argv);
        if (context.isTty()) {
            context.terminal().puts(InfoCmp.Capability.clear_screen, new Object[0]);
            context.terminal().flush();
        }
    }
    
    public static void wc(final Context context, final String[] 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" };
        final Options opt = parseOptions(context, usage, argv);
        final List<Source> sources = new ArrayList<Source>();
        if (opt.args().isEmpty()) {
            opt.args().add("-");
        }
        for (final String arg : opt.args()) {
            if ("-".equals(arg)) {
                sources.add(new Source.StdInSource(context.in()));
            }
            else {
                sources.add(new Source.URLSource(context.currentDir().resolve(arg).toUri().toURL(), arg));
            }
        }
        boolean showLines = opt.isSet("lines");
        boolean showWords = opt.isSet("words");
        final boolean showChars = opt.isSet("chars");
        boolean showBytes = opt.isSet("bytes");
        if (!showLines && !showWords && !showChars && !showBytes) {
            showWords = (showLines = (showBytes = true));
        }
        long totalLines = 0L;
        long totalWords = 0L;
        long totalChars = 0L;
        long totalBytes = 0L;
        for (final Source source : sources) {
            long lines = 0L;
            long words = 0L;
            long chars = 0L;
            long bytes = 0L;
            try (final BufferedReader reader = new BufferedReader(new InputStreamReader(source.read()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    ++lines;
                    chars += line.length() + 1;
                    bytes += line.getBytes().length + 1;
                    final String[] wordArray = line.trim().split("\\s+");
                    if (wordArray.length == 1 && wordArray[0].isEmpty()) {
                        continue;
                    }
                    words += wordArray.length;
                }
            }
            totalLines += lines;
            totalWords += words;
            totalChars += chars;
            totalBytes += bytes;
            final 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);
        }
        if (sources.size() > 1) {
            final StringBuilder result2 = new StringBuilder();
            if (showLines) {
                result2.append(String.format("%8d", totalLines));
            }
            if (showWords) {
                result2.append(String.format("%8d", totalWords));
            }
            if (showChars) {
                result2.append(String.format("%8d", totalChars));
            }
            if (showBytes) {
                result2.append(String.format("%8d", totalBytes));
            }
            result2.append(" total");
            context.out().println(result2);
        }
    }
    
    public static void head(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "head - display first lines of files", "Usage: head [-n lines | -c bytes] [file ...]", "  -? --help                    Show help", "  -n --lines=LINES             Print line counts", "  -c --bytes=BYTES             Print byte counts" };
        final Options opt = parseOptions(context, usage, argv);
        if (opt.isSet("lines") && opt.isSet("bytes")) {
            throw new IllegalArgumentException("usage: head [-n # | -c #] [file ...]");
        }
        int nbLines = Integer.MAX_VALUE;
        int nbBytes = Integer.MAX_VALUE;
        if (opt.isSet("lines")) {
            nbLines = opt.getNumber("lines");
        }
        else if (opt.isSet("bytes")) {
            nbBytes = opt.getNumber("bytes");
        }
        else {
            nbLines = 10;
        }
        List<String> args = opt.args();
        if (args.isEmpty()) {
            args = Collections.singletonList("-");
        }
        boolean first = true;
        for (final String arg : args) {
            if (!first && args.size() > 1) {
                context.out().println();
            }
            if (args.size() > 1) {
                context.out().println("==> " + arg + " <==");
            }
            InputStream is;
            if ("-".equals(arg)) {
                is = context.in();
            }
            else {
                is = context.currentDir().resolve(arg).toUri().toURL().openStream();
            }
            if (nbLines != Integer.MAX_VALUE) {
                try (final BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
                    String line;
                    for (int count = 0; (line = reader.readLine()) != null && count < nbLines; ++count) {
                        context.out().println(line);
                    }
                }
            }
            else {
                final byte[] buffer = new byte[nbBytes];
                final int bytesRead = is.read(buffer);
                if (bytesRead > 0) {
                    context.out().write(buffer, 0, bytesRead);
                }
                is.close();
            }
            first = false;
        }
    }
    
    public static void tail(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "tail - display last lines of files", "Usage: tail [-f] [-q] [-c # | -n #] [file ...]", "  -? --help                    Show help", "  -f --follow                  Do not stop at end of file", "  -F --FOLLOW                  Follow and check for file renaming or rotation", "  -n --lines=LINES             Number of lines to print", "  -c --bytes=BYTES             Number of bytes to print" };
        final Options opt = parseOptions(context, usage, argv);
        if (opt.isSet("lines") && opt.isSet("bytes")) {
            throw new IllegalArgumentException("usage: tail [-f] [-q] [-c # | -n #] [file ...]");
        }
        final int lines = opt.isSet("lines") ? opt.getNumber("lines") : 10;
        final int bytes = opt.isSet("bytes") ? opt.getNumber("bytes") : -1;
        final boolean follow = opt.isSet("follow") || opt.isSet("FOLLOW");
        List<String> args = opt.args();
        if (args.isEmpty()) {
            args = Collections.singletonList("-");
        }
        for (final String arg : args) {
            if (args.size() > 1) {
                context.out().println("==> " + arg + " <==");
            }
            if ("-".equals(arg)) {
                tailInputStream(context, context.in(), lines, bytes);
            }
            else {
                final Path path = context.currentDir().resolve(arg);
                if (bytes > 0) {
                    tailFileBytes(context, path, bytes, follow);
                }
                else {
                    tailFileLines(context, path, lines, follow);
                }
            }
        }
    }
    
    private static void tailInputStream(final Context context, final InputStream is, final int lines, final int bytes) throws IOException {
        if (bytes > 0) {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            final byte[] buffer = new byte[8192];
            int n;
            while ((n = is.read(buffer)) != -1) {
                baos.write(buffer, 0, n);
            }
            final byte[] data = baos.toByteArray();
            final int start = Math.max(0, data.length - bytes);
            context.out().write(data, start, data.length - start);
        }
        else {
            final List<String> allLines = new ArrayList<String>();
            try (final BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    allLines.add(line);
                }
            }
            int i;
            for (int start2 = i = Math.max(0, allLines.size() - lines); i < allLines.size(); ++i) {
                context.out().println(allLines.get(i));
            }
        }
    }
    
    private static void tailFileLines(final Context context, final Path path, final int lines, final boolean follow) throws IOException {
        if (!Files.exists(path, new LinkOption[0])) {
            context.err().println("tail: " + path + ": No such file or directory");
            return;
        }
        final List<String> lastLines = new ArrayList<String>();
        try (final BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                lastLines.add(line);
                if (lastLines.size() > lines) {
                    lastLines.remove(0);
                }
            }
        }
        final Iterator<String> iterator = lastLines.iterator();
        while (iterator.hasNext()) {
            final String line = iterator.next();
            context.out().println(line);
        }
        if (follow) {
            context.err().println("tail: follow mode not yet implemented");
        }
    }
    
    private static void tailFileBytes(final Context context, final Path path, final int bytes, final boolean follow) throws IOException {
        if (!Files.exists(path, new LinkOption[0])) {
            context.err().println("tail: " + path + ": No such file or directory");
            return;
        }
        try (final RandomAccessFile raf = new RandomAccessFile(path.toFile(), "r")) {
            final long fileLength = raf.length();
            final long start = Math.max(0L, fileLength - bytes);
            raf.seek(start);
            final byte[] buffer = new byte[8192];
            int n;
            while ((n = raf.read(buffer)) != -1) {
                context.out().write(buffer, 0, n);
            }
        }
        if (follow) {
            context.err().println("tail: follow mode not yet implemented");
        }
    }
    
    public static void grep(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "grep -  search for PATTERN in each FILE or standard input.", "Usage: grep [OPTIONS] PATTERN [FILES]", "  -? --help                Show help", "  -i --ignore-case         Ignore case distinctions", "  -n --line-number         Prefix each line with line number within its input file", "  -q --quiet, --silent     Suppress all normal output", "  -v --invert-match        Select non-matching lines", "  -w --word-regexp         Select only whole words", "  -x --line-regexp         Select only whole lines", "  -c --count               Only print a count of matching lines per file", "     --color=WHEN          Use markers to distinguish the matching string, may be `always', `never' or `auto'", "  -B --before-context=NUM  Print NUM lines of leading context before matching lines", "  -A --after-context=NUM   Print NUM lines of trailing context after matching lines", "  -C --context=NUM         Print NUM lines of output context", "     --pad-lines           Pad line numbers" };
        final Options opt = parseOptions(context, usage, argv);
        final Map<String, String> colorMap = getColorMap(context, "GREP", "mt=1;31:fn=35:ln=32:se=36");
        final List<String> args = opt.args();
        if (args.isEmpty()) {
            throw new IllegalArgumentException("no pattern supplied");
        }
        String regexp;
        final String regex = regexp = args.remove(0);
        if (opt.isSet("word-regexp")) {
            regexp = "\\b" + regexp + "\\b";
        }
        if (opt.isSet("line-regexp")) {
            regexp = "^" + regexp + "$";
        }
        else {
            regexp = ".*" + regexp + ".*";
        }
        Pattern p;
        Pattern p2;
        if (opt.isSet("ignore-case")) {
            p = Pattern.compile(regexp, 2);
            p2 = Pattern.compile(regex, 2);
        }
        else {
            p = Pattern.compile(regexp);
            p2 = Pattern.compile(regex);
        }
        int after = opt.isSet("after-context") ? opt.getNumber("after-context") : -1;
        int before = opt.isSet("before-context") ? opt.getNumber("before-context") : -1;
        final int contextLines = opt.isSet("context") ? opt.getNumber("context") : 0;
        final String lineFmt = opt.isSet("pad-lines") ? "%6d" : "%d";
        if (after < 0) {
            after = contextLines;
        }
        if (before < 0) {
            before = contextLines;
        }
        final boolean count = opt.isSet("count");
        final boolean quiet = opt.isSet("quiet");
        final boolean invert = opt.isSet("invert-match");
        final boolean lineNumber = opt.isSet("line-number");
        final String s;
        final String color = s = (opt.isSet("color") ? opt.get("color") : "auto");
        boolean colored = false;
        switch (s) {
            case "always":
            case "yes":
            case "force": {
                colored = true;
                break;
            }
            case "never":
            case "no":
            case "none": {
                colored = false;
                break;
            }
            case "auto":
            case "tty":
            case "if-tty": {
                colored = context.isTty();
                break;
            }
            default: {
                throw new IllegalArgumentException("invalid argument '" + color + "' for '--color'");
            }
        }
        final Map<String, String> colors = colored ? ((colorMap != null) ? colorMap : getColorMap("mt=1;31:fn=35:ln=32:se=36")) : Collections.emptyMap();
        if (args.isEmpty()) {
            args.add("-");
        }
        final List<GrepSource> sources = new ArrayList<GrepSource>();
        for (final String arg : args) {
            if ("-".equals(arg)) {
                sources.add(new GrepSource(context.in(), "(standard input)"));
            }
            else {
                final Path path = context.currentDir().resolve(arg);
                sources.add(new GrepSource(path, arg));
            }
        }
        boolean match = false;
        for (final GrepSource src : sources) {
            final List<String> lines = new ArrayList<String>();
            boolean firstPrint = true;
            int nb = 0;
            try (final InputStream is = src.getInputStream()) {
                try (final BufferedReader r = new BufferedReader(new InputStreamReader(is))) {
                    int lineno = 1;
                    int lineMatch = 0;
                    String line;
                    while ((line = r.readLine()) != null) {
                        boolean matches = p.matcher(line).matches();
                        if (invert) {
                            matches = !matches;
                        }
                        final AttributedStringBuilder sbl = new AttributedStringBuilder();
                        if (matches) {
                            ++nb;
                            if (!count && !quiet) {
                                if (sources.size() > 1) {
                                    if (colored) {
                                        applyStyle(sbl, colors, "fn");
                                    }
                                    sbl.append(src.getName());
                                    if (colored) {
                                        applyStyle(sbl, colors, "se");
                                    }
                                    sbl.append(":");
                                }
                                if (lineNumber) {
                                    if (colored) {
                                        applyStyle(sbl, colors, "ln");
                                    }
                                    sbl.append(String.format(lineFmt, lineno));
                                    if (colored) {
                                        applyStyle(sbl, colors, "se");
                                    }
                                    sbl.append(":");
                                }
                                if (colored) {
                                    final Matcher matcher2 = p2.matcher(line);
                                    int cur = 0;
                                    while (matcher2.find()) {
                                        sbl.append(line, cur, matcher2.start());
                                        applyStyle(sbl, colors, "ms");
                                        sbl.append(line, matcher2.start(), matcher2.end());
                                        applyStyle(sbl, colors, "se");
                                        cur = matcher2.end();
                                    }
                                    sbl.append(line, cur, line.length());
                                }
                                else {
                                    sbl.append(line);
                                }
                                lineMatch = before + 1;
                            }
                        }
                        else if (lineMatch > 0) {
                            --lineMatch;
                            if (sources.size() > 1) {
                                if (colored) {
                                    applyStyle(sbl, colors, "fn");
                                }
                                sbl.append(src.getName());
                                if (colored) {
                                    applyStyle(sbl, colors, "se");
                                }
                                sbl.append("-");
                            }
                            if (lineNumber) {
                                if (colored) {
                                    applyStyle(sbl, colors, "ln");
                                }
                                sbl.append(String.format(lineFmt, lineno));
                                if (colored) {
                                    applyStyle(sbl, colors, "se");
                                }
                                sbl.append("-");
                            }
                            sbl.append(line);
                        }
                        else {
                            if (sources.size() > 1) {
                                if (colored) {
                                    applyStyle(sbl, colors, "fn");
                                }
                                sbl.append(src.getName());
                                if (colored) {
                                    applyStyle(sbl, colors, "se");
                                }
                                sbl.append("-");
                            }
                            if (lineNumber) {
                                if (colored) {
                                    applyStyle(sbl, colors, "ln");
                                }
                                sbl.append(String.format(lineFmt, lineno));
                                if (colored) {
                                    applyStyle(sbl, colors, "se");
                                }
                                sbl.append("-");
                            }
                            sbl.append(line);
                            while (lines.size() > before) {
                                lines.remove(0);
                            }
                            lineMatch = 0;
                        }
                        lines.add(sbl.toAnsi(context.terminal()));
                        while (lineMatch == 0 && lines.size() > before) {
                            lines.remove(0);
                        }
                        ++lineno;
                    }
                    if (!count && lineMatch > 0) {
                        if (!firstPrint && before + after > 0) {
                            final AttributedStringBuilder sbl2 = new AttributedStringBuilder();
                            if (colored) {
                                applyStyle(sbl2, colors, "se");
                            }
                            sbl2.append("--");
                            context.out().println(sbl2.toAnsi(context.terminal()));
                        }
                        else {
                            firstPrint = false;
                        }
                        for (int i = 0; i < lineMatch + after && i < lines.size(); ++i) {
                            context.out().println(lines.get(i));
                        }
                    }
                    if (count) {
                        context.out().println(nb);
                    }
                    match |= (nb > 0);
                }
                if (is == null) {
                    continue;
                }
            }
        }
    }
    
    public static void sort(final Context context, final String[] 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" };
        final Options opt = parseOptions(context, usage, argv);
        final List<String> args = opt.args();
        final List<String> lines = new ArrayList<String>();
        if (!args.isEmpty()) {
            for (final String filename : args) {
                try (final BufferedReader reader = new BufferedReader(new InputStreamReader(context.currentDir().toUri().resolve(filename).toURL().openStream()))) {
                    readLines(reader, lines);
                }
            }
        }
        else {
            final BufferedReader r = new BufferedReader(new InputStreamReader(context.in()));
            readLines(r, lines);
        }
        final String separator = opt.get("field-separator");
        final boolean caseInsensitive = opt.isSet("ignore-case");
        final boolean reverse = opt.isSet("reverse");
        final boolean ignoreBlanks = opt.isSet("ignore-leading-blanks");
        final boolean numeric = opt.isSet("numeric-sort");
        final boolean unique = opt.isSet("unique");
        final List<String> sortFields = opt.getList("key");
        final 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 (final String s : lines) {
            if (!unique || last == null || !s.equals(last)) {
                context.out().println(s);
            }
            last = s;
        }
    }
    
    public static void ls(final Context context, final String[] argv) throws Exception {
        final String[] usage = { "ls - list files", "Usage: ls [OPTIONS] [PATTERNS...]", "  -? --help                show help", "  -1                       list one entry per line", "  -C                       multi-column output", "     --color=WHEN          colorize the output, may be `always', `never' or `auto'", "  -a                       list entries starting with .", "  -F                       append file type indicators", "  -m                       comma separated", "  -l                       long listing", "  -S                       sort by size", "  -f                       output is not sorted", "  -r                       reverse sort order", "  -t                       sort by modification time", "  -x                       sort horizontally", "  -L                       list referenced file for links", "  -h                       print sizes in human readable form" };
        final Options opt = parseOptions(context, usage, argv);
        final Map<String, String> colorMap = getLsColorMap(context);
        final String s;
        final String color = s = (opt.isSet("color") ? opt.get("color") : "auto");
        boolean colored = false;
        switch (s) {
            case "always":
            case "yes":
            case "force": {
                colored = true;
                break;
            }
            case "never":
            case "no":
            case "none": {
                colored = false;
                break;
            }
            case "auto":
            case "tty":
            case "if-tty": {
                colored = context.isTty();
                break;
            }
            default: {
                throw new IllegalArgumentException("invalid argument '" + color + "' for '--color'");
            }
        }
        final Map<String, String> colors = colored ? ((colorMap != null) ? colorMap : getLsColorMap("dr=1;91:ex=1;92:sl=1;96:ot=34;43")) : Collections.emptyMap();
        final Path currentDir = context.currentDir();
        final List<Path> expanded = new ArrayList<Path>();
        if (opt.args().isEmpty()) {
            expanded.add(currentDir);
        }
        else {
            opt.args().forEach(s -> expanded.add(currentDir.resolve(s)));
        }
        final boolean listAll = opt.isSet("a");
        final Predicate<Path> filter = p -> listAll || p.getFileName().toString().equals(".") || p.getFileName().toString().equals("..") || !p.getFileName().toString().startsWith(".");
        class PathEntry implements Comparable<PathEntry>
        {
            final Path abs = p;
            final Path path;
            final Map<String, Object> attributes;
            final /* synthetic */ Map val$colors;
            
            public PathEntry(final Path abs, final Path root, final Path val$colors) {
                this.val$colors = (Map)val$colors;
                this.path = (abs.startsWith(root) ? root.relativize(abs) : abs);
                this.attributes = this.readAttributes(abs);
            }
            
            @Override
            public int compareTo(final PathEntry o) {
                final int c = this.doCompare(o);
                return ((Options)colors).isSet("r") ? (-c) : c;
            }
            
            private int doCompare(final PathEntry o) {
                if (((Options)colors).isSet("f")) {
                    return -1;
                }
                if (((Options)colors).isSet("S")) {
                    final long s0 = (this.attributes.get("size") != null) ? this.attributes.get("size").longValue() : 0L;
                    final long s2 = (o.attributes.get("size") != null) ? o.attributes.get("size").longValue() : 0L;
                    return (s0 > s2) ? -1 : ((s0 < s2) ? 1 : this.path.toString().compareTo(o.path.toString()));
                }
                if (((Options)colors).isSet("t")) {
                    final long t0 = (this.attributes.get("lastModifiedTime") != null) ? this.attributes.get("lastModifiedTime").toMillis() : 0L;
                    final long t2 = (o.attributes.get("lastModifiedTime") != null) ? o.attributes.get("lastModifiedTime").toMillis() : 0L;
                    return (t0 > t2) ? -1 : ((t0 < t2) ? 1 : this.path.toString().compareTo(o.path.toString()));
                }
                return this.path.toString().compareTo(o.path.toString());
            }
            
            boolean isNotDirectory() {
                return this.is("isRegularFile") || this.is("isSymbolicLink") || this.is("isOther");
            }
            
            boolean isDirectory() {
                return this.is("isDirectory");
            }
            
            private boolean is(final String attr) {
                final Object d = this.attributes.get(attr);
                return d instanceof Boolean && (boolean)d;
            }
            
            String display() {
                String link = "";
                String type;
                String suffix;
                if (this.is("isSymbolicLink")) {
                    type = "sl";
                    suffix = "@";
                    try {
                        final Path l = Files.readSymbolicLink(this.abs);
                        link = " -> " + l.toString();
                    }
                    catch (final IOException ex) {}
                }
                else if (this.is("isDirectory")) {
                    type = "dr";
                    suffix = "/";
                }
                else if (this.is("isExecutable")) {
                    type = "ex";
                    suffix = "*";
                }
                else if (this.is("isOther")) {
                    type = "ot";
                    suffix = "";
                }
                else {
                    type = "";
                    suffix = "";
                }
                final boolean addSuffix = ((Options)colors).isSet("F");
                return PosixCommands.applyStyle(this.path.toString(), this.val$colors, type) + (addSuffix ? suffix : "") + link;
            }
            
            String longDisplay() {
                String username;
                if (this.attributes.containsKey("owner")) {
                    username = Objects.toString(this.attributes.get("owner"), null);
                }
                else {
                    username = "owner";
                }
                if (username.length() > 8) {
                    username = username.substring(0, 8);
                }
                else {
                    for (int i = username.length(); i < 8; ++i) {
                        username += " ";
                    }
                }
                String group;
                if (this.attributes.containsKey("group")) {
                    group = Objects.toString(this.attributes.get("group"), null);
                }
                else {
                    group = "group";
                }
                if (group.length() > 8) {
                    group = group.substring(0, 8);
                }
                else {
                    for (int j = group.length(); j < 8; ++j) {
                        group += " ";
                    }
                }
                Number length = this.attributes.get("size");
                if (length == null) {
                    length = 0L;
                }
                String lengthString;
                if (((Options)colors).isSet("h")) {
                    double l = (double)length.longValue();
                    String unit = "B";
                    if (l >= 1000.0) {
                        l /= 1024.0;
                        unit = "K";
                        if (l >= 1000.0) {
                            l /= 1024.0;
                            unit = "M";
                            if (l >= 1000.0) {
                                l /= 1024.0;
                                unit = "T";
                            }
                        }
                    }
                    if (l < 10.0 && length.longValue() > 1000L) {
                        lengthString = String.format("%.1f", l) + unit;
                    }
                    else {
                        lengthString = String.format("%3.0f", l) + unit;
                    }
                }
                else {
                    lengthString = String.format("%1$8s", length);
                }
                Set<PosixFilePermission> perms = this.attributes.get("permissions");
                if (perms == null) {
                    perms = EnumSet.noneOf(PosixFilePermission.class);
                }
                return (this.is("isDirectory") ? "d" : (this.is("isSymbolicLink") ? "l" : (this.is("isOther") ? "o" : "-"))) + PosixFilePermissions.toString(perms) + " " + String.format("%3s", this.attributes.containsKey("nlink") ? this.attributes.get("nlink").toString() : "1") + " " + username + " " + group + " " + lengthString + " " + this.toString(this.attributes.get("lastModifiedTime")) + " " + this.display();
            }
            
            protected String toString(final FileTime time) {
                final long millis = (time != null) ? time.toMillis() : -1L;
                if (millis < 0L) {
                    return "------------";
                }
                final ZonedDateTime dt = Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault());
                if (System.currentTimeMillis() - millis < 15811200000L) {
                    return DateTimeFormatter.ofPattern("MMM ppd HH:mm").format(dt);
                }
                return DateTimeFormatter.ofPattern("MMM ppd  yyyy").format(dt);
            }
            
            protected Map<String, Object> readAttributes(final Path path) {
                final Map<String, Object> attrs = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
                for (final String view : path.getFileSystem().supportedFileAttributeViews()) {
                    try {
                        final Map<String, Object> attributes;
                        final Map<String, Object> ta = attributes = Files.readAttributes(path, view + ":*", getLinkOptions(((Options)colors).isSet("L")));
                        final Map<String, Object> obj = attrs;
                        Objects.requireNonNull((TreeMap)obj);
                        attributes.forEach(obj::putIfAbsent);
                    }
                    catch (final IOException ex) {}
                }
                attrs.computeIfAbsent("isExecutable", s -> Files.isExecutable(path));
                attrs.computeIfAbsent("permissions", s -> getPermissionsFromFile(path));
                return attrs;
            }
        }
        final List<PathEntry> all = expanded.stream().filter((Predicate<? super Object>)filter).map(p -> new PathEntry(currentDir, opt)).sorted().collect((Collector<? super Object, ?, List<PathEntry>>)Collectors.toList());
        final List<PathEntry> files = all.stream().filter(PathEntry::isNotDirectory).collect((Collector<? super Object, ?, List<PathEntry>>)Collectors.toList());
        final PrintStream out = context.out();
        final Consumer<Stream<PathEntry>> display = s -> {
            boolean optLine = opt.isSet("1");
            final boolean optComma = opt.isSet("m");
            final boolean optLong = opt.isSet("l");
            boolean optCol = opt.isSet("C");
            if (!optLine && !optComma && !optLong && !optCol) {
                if (context.isTty()) {
                    optCol = true;
                }
                else {
                    optLine = true;
                }
            }
            if (optLine) {
                s.map(PathEntry::display);
                Objects.requireNonNull(out);
                final Stream stream;
                stream.forEach(out::println);
            }
            else if (optComma) {
                out.println(s.map(PathEntry::display).collect((Collector<? super Object, ?, String>)Collectors.joining(", ")));
            }
            else if (optLong) {
                s.map(PathEntry::longDisplay);
                Objects.requireNonNull(out);
                final Stream stream2;
                stream2.forEach(out::println);
            }
            else if (optCol) {
                toColumn(context, out, s.map(PathEntry::display), opt.isSet("x"));
            }
            return;
        };
        boolean space = false;
        if (!files.isEmpty()) {
            display.accept(files.stream());
            space = true;
        }
        final List<PathEntry> directories = all.stream().filter(PathEntry::isDirectory).collect((Collector<? super Object, ?, List<PathEntry>>)Collectors.toList());
        for (final PathEntry entry : directories) {
            if (space) {
                out.println();
            }
            space = true;
            final Path path = currentDir.resolve(entry.path);
            if (expanded.size() > 1) {
                out.println(currentDir.relativize(path).toString() + ":");
            }
            try (final Stream<Path> pathStream = Files.list(path)) {
                final Consumer<Stream<PathEntry>> consumer = display;
                final Stream<String> of = Stream.of(new String[] { ".", ".." });
                final Path obj = path;
                Objects.requireNonNull(obj);
                consumer.accept(Stream.concat((Stream<?>)of.map((Function<? super String, ?>)obj::resolve), (Stream<?>)pathStream).filter((Predicate<? super Object>)filter).map(p -> new PathEntry(path, opt)).sorted());
                if (pathStream == null) {
                    continue;
                }
            }
        }
    }
    
    private static void toColumn(final Context context, final PrintStream out, final Stream<String> ansi, final boolean horizontal) {
        final Terminal terminal = context.terminal();
        final int width = context.isTty() ? terminal.getWidth() : 80;
        final List<AttributedString> strings = ansi.map((Function<? super String, ?>)AttributedString::fromAnsi).collect((Collector<? super Object, ?, List<AttributedString>>)Collectors.toList());
        if (!strings.isEmpty()) {
            int max;
            int c;
            for (max = strings.stream().mapToInt(AttributedCharSequence::columnLength).max().getAsInt(), c = Math.max(1, width / max); c > 1 && c * max + (c - 1) >= width; --c) {}
            final int columns = c;
            final int lines = (strings.size() + columns - 1) / columns;
            IntBinaryOperator index;
            if (horizontal) {
                final int i;
                final int j;
                index = ((i, j) -> i * columns + j);
            }
            else {
                final int i;
                final int j;
                index = ((i, j) -> j * lines + i);
            }
            final AttributedStringBuilder sb = new AttributedStringBuilder();
            for (int i = 0; i < lines; ++i) {
                for (int j = 0; j < columns; ++j) {
                    final int idx = index.applyAsInt(i, j);
                    if (idx < strings.size()) {
                        final AttributedString str = strings.get(idx);
                        final boolean hasRightItem = j < columns - 1 && index.applyAsInt(i, j + 1) < strings.size();
                        sb.append(str);
                        if (hasRightItem) {
                            for (int k = 0; k <= max - str.length(); ++k) {
                                sb.append(' ');
                            }
                        }
                    }
                }
                sb.append('\n');
            }
            out.print(sb.toAnsi(terminal));
        }
    }
    
    private static String formatHumanReadable(final long bytes) {
        if (bytes < 1024L) {
            return bytes + "B";
        }
        final int exp = (int)(Math.log((double)bytes) / Math.log(1024.0));
        final String pre = "KMGTPE".charAt(exp - 1) + "";
        return String.format("%.1f%s", bytes / Math.pow(1024.0, exp), pre);
    }
    
    private static void readLines(final BufferedReader reader, final List<String> lines) throws IOException {
        String line;
        while ((line = reader.readLine()) != null) {
            lines.add(line);
        }
    }
    
    public static Map<String, String> getLsColorMap(final String colorString) {
        return getColorMap((colorString != null) ? colorString : "dr=1;91:ex=1;92:sl=1;96:ot=34;43");
    }
    
    public static Map<String, String> getColorMap(final String colorString) {
        final String str = (colorString != null) ? colorString : "";
        if (str.isEmpty()) {
            return Collections.emptyMap();
        }
        final String sep = str.matches("[a-z]{2}=[0-9]*(;[0-9]+)*(:[a-z]{2}=[0-9]*(;[0-9]+)*)*") ? ":" : " ";
        return Arrays.stream(str.split(sep)).collect(Collectors.toMap(s -> s.substring(0, s.indexOf(61)), s -> s.substring(s.indexOf(61) + 1)));
    }
    
    public static Map<String, String> getLsColorMap(final Context session) {
        return getColorMap(session, "LS", "dr=1;91:ex=1;92:sl=1;96:ot=34;43");
    }
    
    public static Map<String, String> getColorMap(final Context session, final String name, final String def) {
        Objects.requireNonNull(session);
        return getColorMap(session::get, name, def);
    }
    
    public static Map<String, String> getColorMap(final Function<String, Object> variables, final String name, final String def) {
        final Object obj = variables.apply(name + "_COLORS");
        String str = (obj != null) ? obj.toString() : null;
        if (str == null) {
            str = def;
        }
        final String sep = str.matches("[a-z]{2}=[0-9]*(;[0-9]+)*(:[a-z]{2}=[0-9]*(;[0-9]+)*)*") ? ":" : " ";
        return Arrays.stream(str.split(sep)).collect(Collectors.toMap(s -> s.substring(0, s.indexOf(61)), s -> s.substring(s.indexOf(61) + 1)));
    }
    
    public static String applyStyle(final String text, final Map<String, String> colors, final String... types) {
        String t = null;
        for (final String type : types) {
            if (colors.get(type) != null) {
                t = type;
                break;
            }
        }
        Objects.requireNonNull(colors);
        return new AttributedString(text, new StyleResolver(colors::get).resolve("." + t)).toAnsi();
    }
    
    public static void applyStyle(final AttributedStringBuilder sb, final Map<String, String> colors, final String... types) {
        String t = null;
        for (final String type : types) {
            if (colors.get(type) != null) {
                t = type;
                break;
            }
        }
        Objects.requireNonNull(colors);
        sb.style(new StyleResolver(colors::get).resolve("." + t));
    }
    
    private static LinkOption[] getLinkOptions(final boolean followLinks) {
        if (followLinks) {
            return PosixCommands.EMPTY_LINK_OPTIONS;
        }
        return PosixCommands.NO_FOLLOW_OPTIONS.clone();
    }
    
    private static boolean isWindowsExecutable(final String fileName) {
        if (fileName == null || fileName.length() <= 0) {
            return false;
        }
        for (final String suffix : PosixCommands.WINDOWS_EXECUTABLE_EXTENSIONS) {
            if (fileName.endsWith(suffix)) {
                return true;
            }
        }
        return false;
    }
    
    private static Set<PosixFilePermission> getPermissionsFromFile(final Path f) {
        try {
            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(f, new LinkOption[0]);
            if (OSUtils.IS_WINDOWS && isWindowsExecutable(f.getFileName().toString())) {
                perms = new HashSet<PosixFilePermission>(perms);
                perms.add(PosixFilePermission.OWNER_EXECUTE);
                perms.add(PosixFilePermission.GROUP_EXECUTE);
                perms.add(PosixFilePermission.OTHERS_EXECUTE);
            }
            return perms;
        }
        catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    protected static Options parseOptions(final Context context, final String[] usage, final Object[] argv) throws Exception {
        final Options opt = Options.compile(usage, s -> get(context, s)).parse(argv, true);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        return opt;
    }
    
    protected static String get(final Context context, final String name) {
        final Object o = context.get(name);
        return (o != null) ? o.toString() : null;
    }
    
    static {
        NO_FOLLOW_OPTIONS = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
        WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList((List<? extends String>)Arrays.asList(".bat", ".exe", ".cmd"));
        EMPTY_LINK_OPTIONS = new LinkOption[0];
    }
    
    public static class Context
    {
        private final InputStream in;
        private final PrintStream out;
        private final PrintStream err;
        private final Path currentDir;
        private final Terminal terminal;
        private final Function<String, Object> variables;
        
        public Context(final InputStream in, final PrintStream out, final PrintStream err, final Path currentDir, final Terminal terminal, final Function<String, Object> variables) {
            this.in = in;
            this.out = out;
            this.err = err;
            this.currentDir = currentDir;
            this.terminal = terminal;
            this.variables = variables;
        }
        
        public InputStream in() {
            return this.in;
        }
        
        public PrintStream out() {
            return this.out;
        }
        
        public PrintStream err() {
            return this.err;
        }
        
        public Path currentDir() {
            return this.currentDir;
        }
        
        public Terminal terminal() {
            return this.terminal;
        }
        
        public boolean isTty() {
            return this.terminal != null;
        }
        
        public Object get(final String name) {
            return this.variables.apply(name);
        }
    }
    
    public static class SortComparator implements Comparator<String>
    {
        private static Pattern fpPattern;
        private boolean caseInsensitive;
        private boolean reverse;
        private boolean ignoreBlanks;
        private boolean numeric;
        private char separator;
        private List<Key> sortKeys;
        
        public SortComparator(final boolean caseInsensitive, final boolean reverse, final boolean ignoreBlanks, final boolean numeric, final char separator, List<String> sortFields) {
            this.caseInsensitive = caseInsensitive;
            this.reverse = reverse;
            this.separator = separator;
            this.ignoreBlanks = ignoreBlanks;
            this.numeric = numeric;
            if (sortFields == null || sortFields.isEmpty()) {
                sortFields = new ArrayList<String>();
                sortFields.add("1");
            }
            this.sortKeys = sortFields.stream().map(x$0 -> new Key(x$0)).collect((Collector<? super Object, ?, List<Key>>)Collectors.toList());
        }
        
        @Override
        public int compare(final String o1, final String o2) {
            int res = 0;
            final List<Integer> fi1 = this.getFieldIndexes(o1);
            final List<Integer> fi2 = this.getFieldIndexes(o2);
            for (final Key key : this.sortKeys) {
                final int[] k1 = this.getSortKey(o1, fi1, key);
                final int[] k2 = this.getSortKey(o2, fi2, key);
                if (key.numeric) {
                    final Double d1 = this.getDouble(o1, k1[0], k1[1]);
                    final Double d2 = this.getDouble(o2, k2[0], k2[1]);
                    res = d1.compareTo(d2);
                }
                else {
                    res = this.compareRegion(o1, k1[0], k1[1], o2, k2[0], k2[1], key.caseInsensitive);
                }
                if (res != 0) {
                    if (key.reverse) {
                        res = -res;
                        break;
                    }
                    break;
                }
            }
            return res;
        }
        
        protected Double getDouble(final String s, final int start, final int end) {
            final String field = s.substring(start, end);
            final Matcher m = SortComparator.fpPattern.matcher(field);
            if (m.find()) {
                return Double.valueOf(field.substring(m.start(1), m.end(1)));
            }
            return 0.0;
        }
        
        protected int compareRegion(final String s1, final int start1, final int end1, final String s2, final int start2, final int end2, final boolean caseInsensitive) {
            for (int i1 = start1, i2 = start2; i1 < end1 && i2 < end2; ++i1, ++i2) {
                char c1 = s1.charAt(i1);
                char c2 = s2.charAt(i2);
                if (c1 != c2) {
                    if (!caseInsensitive) {
                        return c1 - c2;
                    }
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            return c1 - c2;
                        }
                    }
                }
            }
            return end1 - end2;
        }
        
        protected int[] getSortKey(final String str, final List<Integer> fields, final Key key) {
            int start;
            if (key.startField * 2 <= fields.size()) {
                start = fields.get((key.startField - 1) * 2);
                if (key.ignoreBlanksStart) {
                    while (start < fields.get((key.startField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(start))) {
                        ++start;
                    }
                }
                if (key.startChar > 0) {
                    start = Math.min(start + key.startChar - 1, fields.get((key.startField - 1) * 2 + 1));
                }
            }
            else {
                start = 0;
            }
            int end;
            if (key.endField > 0 && key.endField * 2 <= fields.size()) {
                end = fields.get((key.endField - 1) * 2);
                if (key.ignoreBlanksEnd) {
                    while (end < fields.get((key.endField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(end))) {
                        ++end;
                    }
                }
                if (key.endChar > 0) {
                    end = Math.min(end + key.endChar - 1, fields.get((key.endField - 1) * 2 + 1));
                }
            }
            else {
                end = str.length();
            }
            return new int[] { start, end };
        }
        
        protected List<Integer> getFieldIndexes(final String o) {
            final List<Integer> fields = new ArrayList<Integer>();
            if (o.length() > 0) {
                if (this.separator == '\0') {
                    fields.add(0);
                    for (int idx = 1; idx < o.length(); ++idx) {
                        if (Character.isWhitespace(o.charAt(idx)) && !Character.isWhitespace(o.charAt(idx - 1))) {
                            fields.add(idx - 1);
                            fields.add(idx);
                        }
                    }
                    fields.add(o.length() - 1);
                }
                else {
                    int last = -1;
                    for (int idx2 = o.indexOf(this.separator); idx2 >= 0; idx2 = o.indexOf(this.separator, idx2 + 1)) {
                        if (last >= 0) {
                            fields.add(last);
                            fields.add(idx2 - 1);
                        }
                        else if (idx2 > 0) {
                            fields.add(0);
                            fields.add(idx2 - 1);
                        }
                        last = idx2 + 1;
                    }
                    if (last < o.length()) {
                        fields.add(Math.max(last, 0));
                        fields.add(o.length() - 1);
                    }
                }
            }
            return fields;
        }
        
        static {
            final String Digits = "(\\p{Digit}+)";
            final String HexDigits = "(\\p{XDigit}+)";
            final String Exp = "[eE][+-]?(\\p{Digit}+)";
            final String fpRegex = "([\\x00-\\x20]*[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*)(.*)";
            SortComparator.fpPattern = Pattern.compile("([\\x00-\\x20]*[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*)(.*)");
        }
        
        public class Key
        {
            int startField;
            int startChar;
            int endField;
            int endChar;
            boolean ignoreBlanksStart;
            boolean ignoreBlanksEnd;
            boolean caseInsensitive;
            boolean reverse;
            boolean numeric;
            
            public Key(final String str) {
                boolean modifiers = false;
                boolean startPart = true;
                boolean inField = true;
                boolean inChar = false;
                for (final char c : str.toCharArray()) {
                    switch (c) {
                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9': {
                            if (!inField && !inChar) {
                                throw new IllegalArgumentException("Bad field syntax: " + str);
                            }
                            if (startPart) {
                                if (inChar) {
                                    this.startChar = this.startChar * 10 + (c - '0');
                                    break;
                                }
                                this.startField = this.startField * 10 + (c - '0');
                                break;
                            }
                            else {
                                if (inChar) {
                                    this.endChar = this.endChar * 10 + (c - '0');
                                    break;
                                }
                                this.endField = this.endField * 10 + (c - '0');
                                break;
                            }
                            break;
                        }
                        case '.': {
                            if (!inField) {
                                throw new IllegalArgumentException("Bad field syntax: " + str);
                            }
                            inField = false;
                            inChar = true;
                            break;
                        }
                        case 'n': {
                            inField = false;
                            inChar = false;
                            modifiers = true;
                            this.numeric = true;
                            break;
                        }
                        case 'f': {
                            inField = false;
                            inChar = false;
                            modifiers = true;
                            this.caseInsensitive = true;
                            break;
                        }
                        case 'r': {
                            inField = false;
                            inChar = false;
                            modifiers = true;
                            this.reverse = true;
                            break;
                        }
                        case 'b': {
                            inField = false;
                            inChar = false;
                            modifiers = true;
                            if (startPart) {
                                this.ignoreBlanksStart = true;
                                break;
                            }
                            this.ignoreBlanksEnd = true;
                            break;
                        }
                        case ',': {
                            inField = true;
                            inChar = false;
                            startPart = false;
                            break;
                        }
                        default: {
                            throw new IllegalArgumentException("Bad field syntax: " + str);
                        }
                    }
                }
                if (!modifiers) {
                    final boolean access$200 = SortComparator.this.ignoreBlanks;
                    this.ignoreBlanksEnd = access$200;
                    this.ignoreBlanksStart = access$200;
                    this.reverse = SortComparator.this.reverse;
                    this.caseInsensitive = SortComparator.this.caseInsensitive;
                    this.numeric = SortComparator.this.numeric;
                }
                if (this.startField < 1) {
                    throw new IllegalArgumentException("Bad field syntax: " + str);
                }
            }
        }
    }
    
    private static class GrepSource
    {
        private final InputStream inputStream;
        private final Path path;
        private final String name;
        
        public GrepSource(final InputStream inputStream, final String name) {
            this.inputStream = inputStream;
            this.path = null;
            this.name = name;
        }
        
        public GrepSource(final Path path, final String name) {
            this.inputStream = null;
            this.path = path;
            this.name = name;
        }
        
        public InputStream getInputStream() throws IOException {
            if (this.inputStream != null) {
                return this.inputStream;
            }
            return this.path.toUri().toURL().openStream();
        }
        
        public String getName() {
            return this.name;
        }
    }
    
    public interface CommandExecutor
    {
        String execute(final List<String> p0) throws Exception;
    }
}
