@CommandDefinition(name = "cat", description = "concatenate files and print on the standard output")
public class Cat implements Command<CommandInvocation> {

    @Option(shortName = 'A', name = "show-all", hasValue = false, description = "equivalent to -vET")
    private boolean showAll;

    @Option(shortName = 'b', name = "number-nonblank", hasValue = false, description = "number nonempty output lines, overrides -n")
    private boolean numberNonBlank;

    @Option(shortName = 'E', name = "show-ends", hasValue = false, description = "display $ at end of each line")
    private boolean showEnds;

    @Option(shortName = 'n', name = "number", hasValue = false, description = "number all output lines")
    private boolean number;

    @Option(shortName = 's', name = "squeeze-blank", hasValue = false, description = "suppress repeated empty output lines")
    private boolean squeezeBlank;

    @Option(shortName = 'T', name = "show-tabs", hasValue = false, description = "display TAB characters as ^I")
    private boolean showTabs;

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Arguments
    private List<Resource> files;

    private boolean prevBlank = false;
    private boolean currentBlank = false;
    private int counter;

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {

        if(help) {
            commandInvocation.println(
                    commandInvocation.getHelpInfo("cat") );
            return CommandResult.SUCCESS;
        }

        try {
            counter = 1;
            if(showAll) {
                showEnds = true;
                showTabs = true;
            }
            //do we have data from a pipe/redirect?
            /*
            if(commandInvocation.getShell().in().getStdIn().available() > 0) {
                java.util.Scanner s = new java.util.Scanner(commandInvocation.getShell().in().getStdIn()).useDelimiter("\\A");
                String input = s.hasNext() ? s.next() : "";
                commandInvocation.getShell().out().println();
                for(String i : input.split(Config.getLineSeparator()))
                    displayLine(i, commandInvocation.getShell());

                return CommandResult.SUCCESS;
            }
            */
            if(files != null && files.size() > 0) {
                for(Resource f : files)
                    displayFile(f.resolve(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory()).get(0),
                            //PathResolver.resolvePath(f, commandInvocation.getAeshContext().getCurrentWorkingDirectory()).get(0),
                            commandInvocation.getShell());

                return CommandResult.SUCCESS;
            }
            //read from stdin
            else {
                readFromStdin(commandInvocation);

                return CommandResult.SUCCESS;
            }
        }
        catch(FileNotFoundException fnfe) {
            commandInvocation.println("cat: "+fnfe.getMessage());
            return CommandResult.FAILURE;
        }
    }

    private void displayFile(Resource f, Shell shell) throws FileNotFoundException {
        BufferedReader br = new BufferedReader(new InputStreamReader(f.read()));

        try {
            String line = br.readLine();
            while(line != null) {
                if(line.length() == 0) {
                    if(currentBlank && squeezeBlank)
                        prevBlank = true;
                    currentBlank = true;
                }
                else
                    prevBlank = currentBlank = false;

                displayLine(line, shell);

                line = br.readLine();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void displayLine(String line, Shell shell) {
        if(numberNonBlank) {
            if(!currentBlank) {
                shell.write(Parser.padLeft(6, String.valueOf(counter)));
                shell.write(" ");
                counter++;
            }
        }
        else if(number && !prevBlank) {
            shell.write(Parser.padLeft(6, String.valueOf(counter)));
            shell.write(' ');
            counter++;
        }

        if(showTabs) {
            if(line.contains("\t"))
                line = line.replaceAll("\t","^I");
            if(!prevBlank)
                shell.write(line);
        }
        else {
            if(!prevBlank)
                shell.write(line);
        }

        if(showEnds && !prevBlank)
            shell.write('$');

        if(!prevBlank)
            shell.write(Config.getLineSeparator());
    }

    private void readFromStdin(CommandInvocation commandInvocation) {
        try {
            KeyAction input = commandInvocation.input();
            StringBuilder builder = new StringBuilder();
            while(!input.bufferEquals(Key.CTRL_C)) {
                if(input.bufferEquals( Key.ENTER)) {
                    commandInvocation.println("");
                    displayLine(builder.toString(), commandInvocation.getShell());
                    builder = new StringBuilder();
                }
                else {
                    builder.append((char) input.getCodePointAt(0));
                    commandInvocation.getShell().write((char) input.getCodePointAt(0));
                }

                input = commandInvocation.input();
            }

        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
} @CommandDefinition(name = "cd", description = "change directory [dir]")
public class Cd implements Command<CommandInvocation> {

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Arguments(description = "directory to change to")
    private List<Resource> arguments;

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        if (help) {
            commandInvocation.println(commandInvocation.getHelpInfo("cd"));
            return CommandResult.SUCCESS;
        }

        if (arguments == null) {
            updatePrompt(commandInvocation,
                    commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory().newInstance(Config.getHomeDir()));
        }
        else {
            List<Resource> files =
                    arguments.get(0).resolve(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory());

            if(files.get(0).isDirectory())
                updatePrompt(commandInvocation, files.get(0));
        }
        return CommandResult.SUCCESS;
    }

    private void updatePrompt(CommandInvocation commandInvocation, Resource file) {
        commandInvocation.getConfiguration().getAeshContext().setCurrentWorkingDirectory(file);
        commandInvocation.setPrompt(new Prompt("[aesh@extensions:"+file.toString()+"]$ "));
    }
} @CommandDefinition(name = "clear", description = "clear the terminal screen")
public class Clear implements Command<CommandInvocation> {

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        commandInvocation.getShell().clear();
        return CommandResult.SUCCESS;
    }
} @CommandDefinition(name = "echo", description = "Echo the STRING(s) to standard output.")
public class Echo implements Command<CommandInvocation> {

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Arguments
    private List<String> arguments;

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {

        Shell shell = commandInvocation.getShell();

        if (help || arguments == null || arguments.isEmpty()) {
            shell.writeln(commandInvocation.getHelpInfo("echo"));
            return CommandResult.SUCCESS;
        }

        String stdout = "";
        for (String s : arguments) {
            stdout += s + " ";
        }
        stdout = stdout.trim();

        commandInvocation.println(stdout);

        return CommandResult.SUCCESS;
    }

} @CommandDefinition(name = "exit", description = "exit the program")
public class Exit implements Command<CommandInvocation> {

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        commandInvocation.stop();
        return CommandResult.SUCCESS;
    }
} @CommandDefinition(name = "grep",
        description = "[OPTION]... PATTERN [FILE]...\n"+
                "Search for PATTERN in each FILE or standard input.\n"+
                "PATTERN is a regular expression.\n" +
                "Example: grep -i 'hello world' menu.h main.c\n")
public class Grep implements Command<CommandInvocation> {

    @Option(shortName = 'H', name = "help", hasValue = false,
            description = "display this help and exit")
    private boolean help;

//    @Option(shortName = 'E', name = "extended-regexp", hasValue = false,
//            description = "PATTERN is an extended regular expression (ERE)")
//    private boolean extendedRegex;
//
//    @Option(shortName = 'F', name = "fixed-strings", hasValue = false,
//            description = "PATTERN is a set of newline-separated fixed strings")
//    private boolean fixedStrings;
//
//    @Option(shortName = 'G', name = "basic-regexp", hasValue = false,
//            description = "PATTERN is a basic regular expression (BRE)")
//    private boolean basicRegexp;
//
//    @Option(shortName = 'P', name = "perl-regexp", hasValue = false,
//            description = "PATTERN is a Perl regular expression")
//    private boolean perlRegexp;
//
//    @Option(shortName = 'e', name = "regexp", argument = "PATTERN",
//            description = "use PATTERN for matching")
//    private String regexp;
//
//    @Option(shortName = 'f', name = "file", argument = "FILE",
//            description = "obtain PATTERN from FILE")
//    private Resource file;

    @Option(shortName = 'i', name = "ignore-case", hasValue = false,
            description = "ignore case distinctions")
    private boolean ignoreCase;

     @Option(shortName = 'n', name = "line-number", hasValue = false,
            description = "Prefix each line of output with the 1-based line number within its input file.")
    private boolean lineNumber;

     @Option(shortName = 'o', name = "only-matching", hasValue = false,
            description = "Print only the matched (non-empty) parts of a matching line, with each such  part  on  a separate output line.")
    private boolean onlyMatching;

     @Option(shortName = 'c', name = "count", hasValue = false,
            description = "Suppress normal output; instead print a count of matching lines for each input file.")
    private boolean count;

    @Arguments(completer = GrepCompletor.class,
            description = "The pattern to grep followed by file paths.")
    private List<String> arguments;

    private Pattern pattern;

    private int numberOfLines;

    private int numberOfMatchedLines;

    public List<String> getArguments() {
        return arguments;
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
        //just display help and return
        if(help || arguments == null || arguments.size() == 0) {
            commandInvocation.println(commandInvocation.getHelpInfo("grep"), true);
            return CommandResult.SUCCESS;
        }

        //reset numberOfLines if we've used grep before
        numberOfLines = 0;
        numberOfMatchedLines = 0;

        //first create the pattern
        try {
            if(ignoreCase)
                pattern = Pattern.compile(arguments.remove(0), Pattern.CASE_INSENSITIVE);
            else
                pattern = Pattern.compile(arguments.remove(0));
        }
        catch(PatternSyntaxException pse) {
            commandInvocation.println("grep: invalid pattern.");
            return CommandResult.FAILURE;
        }

        try {
            //do we have data from a pipe/redirect?
            if (commandInvocation.getConfiguration().getPipedData() != null
                    && commandInvocation.getConfiguration().getPipedData().available() > 0) {
                java.util.Scanner s = new java.util.Scanner(commandInvocation.getConfiguration().getPipedData()).useDelimiter("\\A");
                String input = s.hasNext() ? s.next() : "";
                for(String line : input.split("\\R")) {
                    numberOfLines++;
                    doGrep(line, commandInvocation);
                }
                if(count)
                    commandInvocation.println(String.valueOf(numberOfMatchedLines), true);

            } //find argument files and build regex..
            else if (arguments != null && arguments.size() > 0) {
                for (String s : arguments) {
                    doGrep(commandInvocation.getConfiguration().getAeshContext().
                            getCurrentWorkingDirectory().newInstance(s),
                            commandInvocation);
                }
            } //posix starts an interactive shell and read from the input here
            //atm, we'll just quit
            else {
                commandInvocation.println("grep: no file or input given.");
                return CommandResult.SUCCESS;
            }
        } catch (IOException ex) {
            throw new CommandException(ex);
        }

        return null;
    }

    private void doGrep(Resource file, CommandInvocation invocation) throws IOException {
        if (!file.exists()) {
            invocation.println("grep: " + file.toString() + ": No such file or directory");
        }
        else if (file.isLeaf()) {
            try (BufferedReader reader = Files.newBufferedReader(Paths.get(file.getAbsolutePath()))) {
                if (pattern != null) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        numberOfLines++;
                        doGrep(line, invocation);
                    }
                    if(count)
                        invocation.println(String.valueOf(numberOfMatchedLines), true);
                }
                else
                    invocation.println("No pattern given");
            }

        }
    }

    private void doGrep(String line, CommandInvocation invocation) {
        if(pattern != null) {
            Matcher matcher = pattern.matcher(line);
            if(matcher.find()) {
                numberOfMatchedLines++;
                //check if count is set, if so it overrides the output
                if(!count) {
                    if(lineNumber) {
                        if (onlyMatching) {
                            invocation.println(numberOfLines + ": " + line.substring(matcher.start(), matcher.end()), true);
                        }
                        else
                            invocation.println(numberOfLines +": "+line, true);
                    }
                    else {
                        if(onlyMatching)
                            invocation.println(line.substring(matcher.start(), matcher.end()), true);
                        else
                            invocation.println(line, true);
                    }
                }
            }
        }
    }

    /**
     * First argument is the pattern
     * All other arguments should be files
     */
    public static class GrepCompletor implements OptionCompleter<CompleterInvocation> {

        @Override
        public void complete(CompleterInvocation completerData) {
            Grep grep = (Grep) completerData.getCommand();
            //the first argument is the pattern, do not autocomplete
            if (grep.getArguments() != null && grep.getArguments().size() > 0) {
                CompleteOperation completeOperation
                        = new AeshCompleteOperation(completerData.getAeshContext(),
                                completerData.getGivenCompleteValue(), 0);
                List<String> candidates = new ArrayList<>();
                if (completerData.getGivenCompleteValue() == null) {
                    new FileLister("", completerData.getAeshContext().getCurrentWorkingDirectory()).
                            findMatchingDirectories(candidates);
                    completeOperation.addCompletionCandidates(candidates);
                } else {
                    int offset = new FileLister(completerData.getGivenCompleteValue(), completerData.getAeshContext().getCurrentWorkingDirectory()).
                            findMatchingDirectories(candidates);
                    completeOperation.addCompletionCandidates(candidates);
                    completeOperation.setOffset(completerData.getGivenCompleteValue().length() - offset);
                }

                if (completeOperation.getCompletionCandidates().size() > 1) {
                    completeOperation.removeEscapedSpacesFromCompletionCandidates();
                }

                completerData.setCompleterValuesTerminalString(completeOperation.getCompletionCandidates());
                completerData.setOffset(completeOperation.getOffset());
                if (completerData.getGivenCompleteValue() != null && completerData.getCompleterValues().size() == 1) {
                    completerData.setAppendSpace(completeOperation.hasAppendSeparator());
                }
            }
        }
    }
} @CommandDefinition(name="harlem", description = "wanna do the harlem..?")
public class Harlem implements Command<CommandInvocation> {

    private int rows;
    private int columns;
    private char[] randomChars = {'@','#','$','%','&','{','}','?','-','/','\\'};
    private Color[] randomColors = {Color.GREEN, Color.BLUE, Color.RED, Color.YELLOW, Color.DEFAULT};
    private Random random;
    private TerminalCharacter[][] terminalCharacters;
    private boolean allowDownload = false;
    private File harlemWav = new File(Config.getTmpDir()+Config.getPathSeparator()+"harlem.wav");
    private CommandInvocation commandInvocation;

    public Harlem() {
        random = new Random();
    }

    public void afterAttach() {
        rows = getShell().size().getHeight();
        columns = getShell().size().getWidth();
        terminalCharacters = new TerminalCharacter[rows][columns];

        getShell().write(ANSI.ALTERNATE_BUFFER);
        if(!harlemWav.isFile())
            displayQuestion();
        else
            startHarlem();
    }

    private Shell getShell() {
        return commandInvocation.getShell();
    }

    private void displayQuestion() {
        getShell().write(ANSI.START +rows+";1H");
        getShell().write("Allow harlem to save file to \""+Config.getTmpDir()+"? (y or n)");
        try {
            processOperation(commandInvocation.input());
        }
        catch (InterruptedException e) {
            afterDetach();
        }
    }

    protected void afterDetach() {
        getShell().write(ANSI.MAIN_BUFFER);
        getShell().write(ANSI.START+"?25h");
    }

    private void displayWait() {
        getShell().write(ANSI.START+"?25l");
        getShell().write(ANSI.START+rows/2+";1H");
        getShell().write("Buffering....  please wait.....");
    }

    private void displayIntro() {
        getShell().write(ANSI.START + "1;1H");
        TerminalCharacter startChar = new TerminalCharacter('|');
        for (int i = 0; i < terminalCharacters.length; i++) {
            for (int j = 0; j < terminalCharacters[i].length; j++) {
                terminalCharacters[i][j] = startChar;
            }
        }

        for(int i=0; i < terminalCharacters.length; i++) {
            StringBuilder sb = new StringBuilder();
            for(int j=0; j < terminalCharacters[i].length; j++) {
                if(j > 0)
                    sb.append(terminalCharacters[i][j].toString(terminalCharacters[i][j]));
                else
                    sb.append(terminalCharacters[i][j].toString());
            }
            getShell().write(sb.toString());
        }

        int middleRow = rows/2;
        int middleColumn = columns/2;
        TerminalCharacter middleChar = terminalCharacters[middleRow][middleColumn];

        for(int i=0; i < 33; i++) {
            try {
                Thread.sleep(450);
            } catch (InterruptedException e) {
                //ignored
            }
            getShell().write(ANSI.START+middleRow+";"+middleColumn+"H");
            getShell().write(middleChar.toString());
            middleChar = new TerminalCharacter(getNextChar(middleChar.getCharacter()));
        }

        displayHarlem();
    }

    private char getNextChar(char prev) {
        if(prev == '|')
            return '/';
        else if(prev == '/')
            return '-';
        else if(prev == '-')
            return '\\';
        else
            return '|';
    }

    private char getRandomChar() {
        return randomChars[ random.nextInt(randomChars.length)];
    }

    private Color getRandomColor() {
        return randomColors[ random.nextInt(randomColors.length)];
    }

    private void displayHarlem() {
         for(int i=0; i < 22; i++) {
            try {
                Thread.sleep(630);
            } catch (InterruptedException e) {
                //ignored
            }
            displayCorus();
        }
    }

    private void displayCorus() {
        getShell().write(ANSI.START+"1;1H");
        StringBuilder sb = new StringBuilder();
        for(int i=0; i < terminalCharacters.length; i++) {
            for(int j=0; j < terminalCharacters[i].length;j++) {
                if(j % 2 == 0)
                    sb.append(new TerminalCharacter(' ').toString());
                else
                    sb.append(new TerminalCharacter(getRandomChar(), new TerminalColor(getRandomColor(), Color.DEFAULT)).toString());
            }
        }
        getShell().write(sb.toString());
    }

    public void processOperation(KeyAction operation) {
        if(operation.getCodePointAt(0) == 'y') {
           allowDownload = true;
            startHarlem();
        }
        if(operation.getCodePointAt(0) == 'n') {
            startHarlem();
        }
        if(operation.getCodePointAt(0) == 'q') {
            afterDetach();
        }
    }

    private void startHarlem() {
        displayWait();
        playHarlem();
        displayIntro();
        afterDetach();
    }

    private void playHarlem() {
        try {
            if(!harlemWav.isFile() && allowDownload)
                saveHarlem(harlemWav);
            Clip clip = AudioSystem.getClip();
            if(harlemWav.isFile()) {
                clip.open(AudioSystem.getAudioInputStream(harlemWav));
            }
            else {
                clip.open(AudioSystem.getAudioInputStream(new URL("https://dl.dropbox.com/u/30971563/harlem.wav")));
            }
            clip.start();
        }
        catch (Exception exc) {
            exc.printStackTrace(System.out);
        }
    }

    private void saveHarlem(File harlemWav) throws IOException {
        try (BufferedInputStream in = new BufferedInputStream(new URL("https://dl.dropbox.com/u/30971563/harlem.wav").openStream())) {
            byte[] data = new byte[1024];
            int count;
            try (FileOutputStream fout = new FileOutputStream(harlemWav);) {
                while ((count = in.read(data, 0, 1024)) != -1) {
                    fout.write(data, 0, count);
                }
            }
        }
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        this.commandInvocation = commandInvocation;
        afterAttach();
        return CommandResult.SUCCESS;
    }
} @CommandDefinition(name ="less", description = "less is more")
public class Less extends AeshFileDisplayer {

    @Arguments
    List<Resource> arguments;

    @Option(hasValue = false)
    private boolean color;

    private SimpleFileParser loader;

    public Less() {
        super();
    }

    public void setFile(File file) throws IOException {
        loader.setFile(file);
    }

    public void setFile(String filename) throws IOException {
        loader.setFile(filename);
    }

    public void setInput(String input) throws IOException {
        loader.readPageAsString(input);
    }

    public void setFile(InputStream inputStream, String fileName) {
        loader.setFile(inputStream, fileName);
    }

    @Override
    public FileParser getFileParser() {
        return loader;
    }

    @Override
    public void displayBottom() throws IOException {
        if(getSearchStatus() == TerminalPage.Search.SEARCHING) {
            clearBottomLine();
           writeToConsole("/"+getSearchWord());
        }
        else if(getSearchStatus() == TerminalPage.Search.NOT_FOUND) {
            clearBottomLine();
            writeToConsole(ANSI.INVERT_BACKGROUND+
                    "Pattern not found (press RETURN)"+
                    ANSI.DEFAULT_TEXT);
        }
        else if(getSearchStatus() == TerminalPage.Search.RESULT) {
            writeToConsole(":");
        }
        else if(getSearchStatus() == TerminalPage.Search.NO_SEARCH) {
            if(isAtBottom())
                writeToConsole(ANSI.INVERT_BACKGROUND+"(END)"+ANSI.DEFAULT_TEXT);
            else
                writeToConsole(":");
        }
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) throws InterruptedException, CommandException {
        setCommandInvocation(commandInvocation);
        //make sure to reset loader on each execute
        loader = new SimpleFileParser();

        try {
            if (commandInvocation.getConfiguration().hasPipedData() &&
                    commandInvocation.getConfiguration().getPipedData().available() > 0) {
                java.util.Scanner s = new java.util.Scanner(commandInvocation.getConfiguration().getPipedData()).useDelimiter("\\A");
                String fileContent = s.hasNext() ? s.next() : "";
                setInput(fileContent);
                afterAttach();
                return CommandResult.SUCCESS;
            }
        } catch (IOException ex) {
            throw new CommandException(ex);
        }

        if(arguments != null && arguments.size() > 0) {
            Resource f =
                    arguments.get(0).resolve(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory()).get(0);
            if(f.isLeaf()) {
                try {
                    if(color) {
                        String content = readFile(f.read());

                        ByteArrayOutputStream baos = new ByteArrayOutputStream();

                        Syntax.builtIns();
                        Syntax.Builder.create()
                                .encoderType(Encoder.Type.TERMINAL)
                                .output(baos)
                                .scanner(Scanner.Factory.byFileName(f.getName()))
                                .execute(content);

                        setInput(new String(baos.toByteArray()));
                        afterAttach();
                    }
                    else {
                        setFile(f.read(), f.getName());
                        afterAttach();
                    }
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            else if(f.isDirectory()) {
                getShell().writeln(f.getAbsolutePath()+": is a directory");
            }
            else {
                getShell().writeln(f.getAbsolutePath() + ": No such file or directory");
            }
        }

        return CommandResult.SUCCESS;
    }

    private String readFile(InputStream inputStream) throws IOException {
        StringBuilder builder = new StringBuilder();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            builder.append(line).append(Config.getLineSeparator());
        }

        inputStream.close();

        return builder.toString();
    }
} @CommandDefinition(name = "ls", description = "[OPTION]... [FILE]...\n" +
        "List information about the FILEs (the current directory by default).\n" +
        "Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.\n")
public class Ls implements Command<CommandInvocation> {

    @Option(shortName = 'H', name = "help", hasValue = false,
            description = "display this help and exit")
    private boolean help;
    @Option(shortName = 'a', name = "all", hasValue = false,
    description = "do not ignore entries starting with .")
    private boolean all;
    @Option(shortName = 'd', name = "directory", hasValue = false,
            description = "list directory entries instead of contents, and do not dereference symbolic links")
    private boolean directory;
    @Option(shortName = 'l', name = "longlisting", hasValue = false,
            description = "use a long listing format")
    private boolean longListing;

    @Option(shortName = 's', name = "size", hasValue = false,
            description = "print the allocated size of each file, in blocks")
    private boolean size;

    @Option(shortName = 'h', name ="human-readable", hasValue = false,
            description = "with -l, print sizes in human readable format (e.g., 1K 234M 2G)")
    private boolean humanReadable;

    @Arguments
    private List<Resource> arguments;

    private static final char SPACE = ' ';

    private static final Class<? extends BasicFileAttributes> fileAttributes =
            Config.isOSPOSIXCompatible() ? PosixFileAttributes.class : DosFileAttributes.class;

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM dd HH:mm");
    private static final DateFormat OLD_FILE_DATE_FORMAT = new SimpleDateFormat("MMM dd  yyyy");

    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(".#");

    private static final TerminalColor DIRECTORY_COLOR = new TerminalColor(Color.BLUE, Color.DEFAULT, Color.Intensity.BRIGHT);
    private static final TerminalColor SYMBOLIC_LINK_COLOR = new TerminalColor(Color.CYAN, Color.DEFAULT, Color.Intensity.BRIGHT);

    public Ls() {
        DECIMAL_FORMAT.setRoundingMode(RoundingMode.UP);
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        //just display help and return
        if(help) {
            commandInvocation.getShell().writeln(commandInvocation.getHelpInfo("ls"));
            return CommandResult.SUCCESS;
        }

        if(arguments == null) {
            arguments = new ArrayList<>(1);
            arguments.add(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory());
        }

        int counter = 0;
        for(Resource file : arguments) {
            if(counter > 0) {
                commandInvocation.getShell().writeln(Config.getLineSeparator()+file.getName()+":");
            }

            for(Resource f : file.resolve(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory())) {
                if(f.isDirectory())
                    displayDirectory(f, commandInvocation.getShell());
                else if(f.isLeaf())
                    displayFile(f,commandInvocation.getShell());
                else if(!f.exists()) {
                    commandInvocation.getShell().writeln("ls: cannot access "+
                            f.toString()+": No such file or directory");
                }
            }
            counter++;
        }

        return CommandResult.SUCCESS;
    }

    private void displayDirectory(Resource input, Shell shell) {
        List<Resource> files = new ArrayList<>();

        if (all) {
            // add "." and ".." to list of files
            files.add(input.newInstance("."));
            files.add(input.newInstance(".."));
            files.addAll(input.list());

        }
        else {
            files = input.list(new NoDotNamesFilter());
        }

        Collections.sort(files, new PosixFileComparator());

        if (longListing) {
            shell.write(displayLongListing(files));
        }
        else {
            shell.write(Parser.formatDisplayCompactListTerminalString(
                formatFileList(files),
                shell.size().getWidth()));
        }
    }

    private List<TerminalString> formatFileList(List<Resource> fileList) {
        ArrayList<TerminalString> list = new ArrayList<>(fileList.size());
        for(Resource file : fileList) {
            if (file.isSymbolicLink()) {
                list.add(new TerminalString(file.getName(), SYMBOLIC_LINK_COLOR));
            } else if (file.isDirectory()) {
                list.add(new TerminalString(file.getName(), DIRECTORY_COLOR));
            } else {
                list.add(new TerminalString(file.getName()));
            }
        }
        return list;
    }

    private void displayFile(Resource input, Shell shell) {
        if(longListing) {
            List<Resource> resourceList = new ArrayList<>(1);
            resourceList.add(input);
            shell.write(displayLongListing(resourceList));
        }
        else {
            List<Resource> resourceList = new ArrayList<>(1);
            resourceList.add(input);
            shell.write(Parser.formatDisplayListTerminalString(
                    formatFileList(resourceList), shell.size().getHeight(), shell.size().getWidth()));
        }
    }

    private String displayLongListing(List<Resource> files) {

        StringGroup access = new StringGroup(files.size());
        StringGroup size = new StringGroup(files.size());
        StringGroup owner = new StringGroup(files.size());
        StringGroup group = new StringGroup(files.size());
        StringGroup modified = new StringGroup(files.size());

        try {
            int counter = 0;
            for(Resource file : files) {
                BasicFileAttributes attr = file.readAttributes(fileAttributes, LinkOption.NOFOLLOW_LINKS);

                if (Config.isOSPOSIXCompatible()) {
                    access.addString(AeshPosixFilePermissions.toString(((PosixFileAttributes) attr)), counter);
                } else {
                    access.addString("", counter);
                }

                size.addString(makeSizeReadable(attr.size()), counter);

                if (Config.isOSPOSIXCompatible()) {
                    owner.addString(((PosixFileAttributes) attr).owner().getName(), counter);
                    group.addString(((PosixFileAttributes) attr).group().getName(), counter);
                } else {
                    owner.addString("", counter);
                    group.addString("", counter);
                }

                // show year instead of time when file wasn't changed in actual year
                Date lastModifiedTime = new Date(attr.lastModifiedTime().toMillis());

                Calendar lastModifiedCalendar = Calendar.getInstance();
                lastModifiedCalendar.setTime(lastModifiedTime);

                Calendar nowCalendar = Calendar.getInstance();

                if (lastModifiedCalendar.get(Calendar.YEAR) == nowCalendar.get(Calendar.YEAR)) {
                    modified.addString(DATE_FORMAT.format(lastModifiedTime), counter);
                } else {
                    modified.addString(OLD_FILE_DATE_FORMAT.format(lastModifiedTime), counter);
                }

                counter++;
            }
        }
        catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i < files.size(); i++) {
            builder.append(access.getString(i))
                .append(owner.getFormattedStringPadRight(i))
                .append(group.getFormattedStringPadRight(i))
                .append(size.getFormattedString(i))
                .append(SPACE)
                .append(modified.getString(i))
                .append(SPACE);

            if (files.get(i).isSymbolicLink()) {
                builder.append(new TerminalString(files.get(i).getName(), SYMBOLIC_LINK_COLOR));
                builder.append(" -> ");
                try {
                    builder.append(files.get(i).readSymbolicLink());
                } catch (IOException ex) {
                    ex.printStackTrace(); // this should not happen
                }
            } else if (files.get(i).isDirectory()) {
                builder.append(new TerminalString(files.get(i).getName(), DIRECTORY_COLOR));
            } else {
                builder.append(files.get(i).getName());
            }

            builder.append(Config.getLineSeparator());
        }

        return builder.toString();
    }

    private String makeSizeReadable(long size) {
        if(!humanReadable)
            return String.valueOf(size);
        else {
            if(size < 10000)
                return String.valueOf(size);
            else if(size < 10000000) //K
                return DECIMAL_FORMAT.format((double) size/1024)+"K";
            else if(size < 1000000000) //M
                return DECIMAL_FORMAT.format((double) size/(1048576))+"M";
            else
                return DECIMAL_FORMAT.format((double) size/(1048576*1014))+"G";
        }
    }

    class PosixFileComparator implements Comparator<Resource> {
        private PosixFileNameComparator posixFileNameComparator = new PosixFileNameComparator();

        @Override
        public int compare(Resource o1, Resource o2) {
            return posixFileNameComparator.compare(o1.getName(), o2.getName());
        }
    }
} @CommandDefinition(name = "matrix", description = "do you take the blue pill??")
public class Matrix implements Command<CommandInvocation> {

    @Option(shortName = 'h', hasValue = false)
    private boolean help;

    @Option(shortName = 'a', hasValue = false, defaultValue = {"true"})
    private boolean async;

    @Option(shortName = 'f')
    private File file;

    @Option(shortName = 'k', defaultValue = {"false"})
    private boolean knock;

    @Option
    private String knockText;

    @Option(shortName = 's', defaultValue = {"3"})
    private int speed;

    @Option(shortName = 'l', defaultValue = {"true"})
    private boolean logo;

    private ExecutorService executorService;
    private MatrixRunner runner;
    private Shell shell;
    private CommandInvocation commandInvocation;
    private List<String> knockLines;
    private InputStream inputStream;

    public Matrix() {
    }

    public Matrix(int speed, boolean async, boolean knock) {
        this.speed = speed;
        this.async = async;
        this.knock = knock;
    }

    public Matrix(InputStream inputStream, List<String> knockLines) {
        this.inputStream = inputStream;
        this.knockLines = knockLines;
        if(knockLines != null)
            knock = true;
        if(inputStream != null)
            logo = true;
    }

    public Matrix(InputStream inputStream, List<String> knockLines, int speed, boolean async) {
        this.inputStream = inputStream;
        this.knockLines = knockLines;
        this.speed = speed;
        this.async = async;
        if(knockLines != null)
            knock = true;
        if(inputStream != null)
            logo = true;
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) throws InterruptedException {
        if(help) {
            commandInvocation.getShell().writeln(commandInvocation.getHelpInfo("matrix"));
        }
        else {
            shell = commandInvocation.getShell();
            this.commandInvocation = commandInvocation;
            shell.write(ANSI.CURSOR_SAVE);
            shell.write(ANSI.CURSOR_HIDE);
            shell.enableAlternateBuffer();
            startMatrix(shell);
            processOperation();
        }
        return CommandResult.SUCCESS;
    }

    private void startMatrix(Shell shell) {
        setupKnock();
        setupInput();
        runner = new MatrixRunner(shell, knockLines, inputStream, speed, async);
        executorService = Executors.newSingleThreadExecutor();
        executorService.execute(runner);
    }

    public void processOperation() throws InterruptedException {
        try {
            while (true) {
                KeyAction commandOperation = commandInvocation.input();
                if (commandOperation.getCodePointAt(0) > 47 &&
                        commandOperation.getCodePointAt(0) < 58) {
                    if (runner != null)
                        runner.speed(Integer.parseInt(String.valueOf((char) commandOperation.getCodePointAt(0))));
                }
                if (Key.a.equalTo(commandOperation)) {
                    if (runner != null)
                        runner.asynch();
                }
                else if (Key.q.equalTo(commandOperation)) {
                    stop();
                    return;
                }
            }
        }
        catch (InterruptedException ie) {
            stop();
            throw ie;
        }
    }

    private void stop() {
        if(runner != null)
            runner.stop();
        if(executorService != null) {
            executorService.shutdown();
        }

        //need to set it to null
        inputStream = null;

        shell.clear();
        shell.write(ANSI.CURSOR_RESTORE);
        shell.write(ANSI.CURSOR_SHOW);
        shell.enableMainBuffer();
    }

    private void setupKnock() {
        if(knock) {
            if(knockText != null) {
                knockLines = new ArrayList<>();
                knockLines.add(knockText);
            }
            else {
                knockLines = new ArrayList<>();
                knockLines.add("Follow the white rabbit...");
                knockLines.add("Knock, knock, "+System.getProperty("user.name")+"...");
            }
        }
    }

    private void setupInput() {
        if(logo) {
            if(inputStream == null) {
                if(file != null && file.isFile()) {
                    try {
                        inputStream = new FileInputStream(file);
                    }
                    catch (FileNotFoundException ignored) { }
                }
                else
                    inputStream = this.getClass().getClassLoader().getResourceAsStream("aesh_logo.txt");
            }
        }
    }
} @CommandDefinition(name = "mkdir", description = "create directory(ies), if they do not already exist.")
public class Mkdir implements Command<CommandInvocation> {

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Option(shortName = 'p', name = "parents", hasValue = false,
            description = "make parent directories as needed")
    private boolean parents;

    @Option(shortName = 'v', name = "verbose", hasValue = false,
            description = "print a message for each created directory")
    private boolean verbose;

    @Arguments(description = "directory(ies) to create")
    private List<Resource> arguments;

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        if (help || arguments == null || arguments.isEmpty()) {
            commandInvocation.getShell().writeln(commandInvocation.getHelpInfo("mkdir"));
            return CommandResult.SUCCESS;
        }

        for (Resource f : arguments) {
            Resource currentWorkingDirectory = commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory();
            Shell shell = commandInvocation.getShell();

            Resource pathResolved = f.resolve(currentWorkingDirectory).get(0);

            if (parents || f.getName().contains(Config.getPathSeparator())) {
                makeDirs(arguments, pathResolved, shell);
            } else {
                makeDir(pathResolved, shell);
            }
        }

        return CommandResult.SUCCESS;
    }

    private void makeDir(Resource dir, Shell shell) {
        if (!dir.exists()) {
            dir.mkdirs();
            if (verbose) {
                shell.writeln("created directory '" + dir.getName() + "'");
            }
        } else {
            shell.writeln("cannot create directory '" + dir.getName() + "': Directory exists");
        }
    }

    private void makeDirs(List<Resource> resources, Resource dir, Shell shell) {
        if (!dir.exists()) {
            dir.mkdirs();
            if (verbose) {
                for (Resource r : resources) {
                    shell.writeln("created directory '" + r.getName() + "'");
                }
            }
        }
    }

} @CommandDefinition(name="more", description = "is more less?")
public class More implements Command<CommandInvocation> {

    private int rows;
    private int topVisibleRow;
    private int prevTopVisibleRow;
    private StringBuilder number;
    private MorePage page;
    private SimpleFileParser loader;
    private CommandInvocation commandInvocation;

    @Arguments
    private List<Resource> arguments;

    public More() {
        number = new StringBuilder();
    }

    public void setFile(File page) throws IOException {
        loader.setFile(page);
    }

    public void setFile(String filename) throws IOException {
        loader.setFile(new File(filename));
    }

    public void setInput(String input) {
        loader.readPageAsString(input);
    }

    public void setInput(InputStream inputStream, String fileName) {
        loader.setFile(inputStream, fileName);
    }

    protected void afterAttach() {
        rows = commandInvocation.getShell().size().getHeight();
        int columns = commandInvocation.getShell().size().getWidth();
        try {
            page = new MorePage(loader, columns);

            if(commandInvocation.getConfiguration().hasOutputRedirection()) {
            int count=0;
            for(String line : this.page.getLines()) {
                commandInvocation.print(line);
                count++;
                if(count < this.page.size())
                    commandInvocation.print(Config.getLineSeparator());
            }

            page.clear();
            loader = new SimpleFileParser();

            }
            else {

                if (!page.hasData()) {
                    //display help
                } else
                    display(Background.INVERSE);

                processOperation();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected void afterDetach() {
        clearNumber();
        topVisibleRow = prevTopVisibleRow = 0;
        /*
        if(!operator.isRedirectionOut()) {
            commandInvocation.getShell().out().print(Buffer.printAnsi("K"));
            commandInvocation.getShell().out().print(Buffer.printAnsi("1G"));
            commandInvocation.getShell().out().flush();
        }
        */
        page.clear();
        loader = new SimpleFileParser();
    }

    public void processOperation() {
        boolean attach = true;
        try {
            while(attach) {
                KeyAction operation = commandInvocation.input();
                if(Key.q.equalTo(operation)) {
                    attach = false;
                }
                else if( Key.ENTER.equalTo(operation)) {
                    topVisibleRow = topVisibleRow + getNumber();
                    if(topVisibleRow > (page.size()-rows)) {
                        topVisibleRow = page.size()-rows;
                        if(topVisibleRow < 0)
                            topVisibleRow = 0;
                        display(Background.INVERSE);
                        attach = false;
                    }
                    else
                        display(Background.INVERSE);
                    clearNumber();
                }
                // ctrl-f ||  space
                else if(Key.CTRL_F.equalTo(operation) || Key.SPACE.equalTo(operation)) {
                    topVisibleRow = topVisibleRow + rows*getNumber();
                    if(topVisibleRow > (page.size()-rows)) {
                        topVisibleRow = page.size()-rows;
                        if(topVisibleRow < 0)
                            topVisibleRow = 0;
                        display(Background.INVERSE);
                        attach = false;
                    }
                    else
                        display(Background.INVERSE);
                    clearNumber();
                }
                else if(Key.CTRL_B.equalTo(operation)) { // ctrl-b
                    topVisibleRow = topVisibleRow - rows*getNumber();
                    if(topVisibleRow < 0)
                        topVisibleRow = 0;
                    display(Background.INVERSE);
                    clearNumber();
                }
                else if(Character.isDigit((char) operation.getCodePointAt(0))) {
                    number.append(Character.getNumericValue(operation.getCodePointAt(0)));
                }
            }
        }
        catch (InterruptedException ie) {
        }
        afterDetach();
    }

    private void display(Background background) {
        //commandInvocation.clear();
        commandInvocation.getShell().write(ANSI.printAnsi("0G"));
        commandInvocation.getShell().write(ANSI.printAnsi("2K"));
        if(prevTopVisibleRow == 0 && topVisibleRow == 0) {
            for(int i=topVisibleRow; i < (topVisibleRow+rows); i++) {
                if(i < page.size()) {
                    commandInvocation.getShell().write(page.getLine(i));
                    commandInvocation.getShell().write(Config.getLineSeparator());
                }
            }
        }
        else if(prevTopVisibleRow < topVisibleRow) {

            for(int i=prevTopVisibleRow; i < topVisibleRow; i++) {
                commandInvocation.getShell().write(page.getLine(i + rows));
                commandInvocation.getShell().write(Config.getLineSeparator());

            }
            prevTopVisibleRow = topVisibleRow;

        }
        else if(prevTopVisibleRow > topVisibleRow) {
            for(int i=topVisibleRow; i < (topVisibleRow+rows); i++) {
                if(i < page.size()) {
                    commandInvocation.getShell().write(page.getLine(i));
                    commandInvocation.getShell().write(Config.getLineSeparator());
                }
            }
            prevTopVisibleRow = topVisibleRow;
        }
        displayBottom(background);
    }

    private void displayBottom(Background background) {
        if(background == Background.INVERSE) {
            commandInvocation.getShell().write(ANSI.INVERT_BACKGROUND);
            commandInvocation.getShell().write("--More--(");
            commandInvocation.getShell().write(getPercentDisplayed()+"%)");

            commandInvocation.getShell().write(ANSI.NORMAL_BACKGROUND);
        }
    }

    private String getPercentDisplayed() {
        double row = topVisibleRow  + rows;
        if(row > this.page.size())
            row  = this.page.size();
        return String.valueOf((int) ((row / this.page.size()) * 100));
    }

    public void displayHelp() throws IOException {
        commandInvocation.getShell().writeln(Config.getLineSeparator()
                +"Usage: more [options] file...");
    }

    private int getNumber() {
        if(number.length() > 0)
            return Integer.parseInt(number.toString());
        else
            return 1;
    }

    private void clearNumber() {
        number = new StringBuilder();
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) throws CommandException {
        this.commandInvocation = commandInvocation;
        loader = new SimpleFileParser();
        try {
            if (commandInvocation.getConfiguration().hasPipedData() &&
                    commandInvocation.getConfiguration().getPipedData().available() > 0) {
                java.util.Scanner s = new java.util.Scanner(commandInvocation.getConfiguration().getPipedData()).useDelimiter("\\A");
                String fileContent = s.hasNext() ? s.next() : "";
                setInput(fileContent);
                afterAttach();
                return CommandResult.SUCCESS;
            }
        } catch (IOException ex) {
            throw new CommandException(ex);
        }

        if(arguments != null && arguments.size() > 0) {
            Resource f = arguments.get(0);
            f = f.resolve(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory()).get(0);
            try {
                if(f.isLeaf()) {
                    setInput(f.read(), f.getName());
                    afterAttach();
                }
                else if(f.isDirectory()) {
                    commandInvocation.getShell().writeln(f.getAbsolutePath()+": is a directory");
                }
                else {
                    commandInvocation.getShell().writeln(f.getAbsolutePath() + ": No such file or directory");
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        return CommandResult.SUCCESS;
    }

    private static enum Background {
        NORMAL,
        INVERSE
    }

    private class MorePage extends TerminalPage {

        public MorePage(FileParser fileParser, int columns) throws IOException {
            super(fileParser, columns);
        }

    }
} @CommandDefinition(name = "popd", description = "usage: popd [-n]")
public class Popd implements Command<CommandInvocation> {

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) throws InterruptedException {

            /*TODO:
        try {
            Pushd pushd = (Pushd) commandInvocation.getCommandRegistry().getCommand("pushd", "").getParser().getCommand();
            Resource popFile = pushd.popDirectory();
            if(popFile != null) {
                commandInvocation.getAeshContext().setCurrentWorkingDirectory(popFile);
                return CommandResult.SUCCESS;
            }
            else {
                commandInvocation.getShell().out().println("popd: directory stack empty");
                return CommandResult.SUCCESS;
            }
        }
        catch (CommandNotFoundException ignored) { }
            */

        return CommandResult.FAILURE;
    }
} @CommandDefinition(name = "pushd", description = "usage: pushd [dir]")
public class Pushd implements Command<CommandInvocation> {

    @Option(shortName = 'h', hasValue = false)
    private boolean help;

    @Arguments(completer = FileOptionCompleter.class, converter = FileResourceConverter.class)
    private List<Resource> arguments;

    private List<Resource> directories;

    public Pushd() {
        directories = new ArrayList<>();
    }

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) throws InterruptedException {
        if(help) {
            commandInvocation.getShell().writeln( commandInvocation.getHelpInfo("pushd"));
            return CommandResult.SUCCESS;
        }
        else if(arguments != null && arguments.size() > 0) {

            List<Resource> files = arguments.get(0).resolve(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory());

            if(files.get(0).isDirectory()) {
                Resource oldCwd = commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory();
                directories.add(oldCwd);
                commandInvocation.getConfiguration().getAeshContext().setCurrentWorkingDirectory(files.get(0));
                commandInvocation.getShell().writeln(files.get(0)+" "+getDirectoriesAsString());
                return CommandResult.SUCCESS;
            }

            return CommandResult.FAILURE;
        }
        else {
            commandInvocation.getShell().writeln("pushd: no other directory");
            return CommandResult.FAILURE;
        }
    }

    private String getDirectoriesAsString() {
        StringBuilder builder = new StringBuilder();
        for(Resource f : directories) {
            if(builder.length() > 0)
                builder.insert(0, " ");
            builder.insert(0, f.toString());
        }

        return builder.toString();
    }

    public Resource popDirectory() {
        if(directories.size() > 0)
            return directories.remove(directories.size()-1);
        else
            return null;
    }
} @CommandDefinition(name = "pwd", description = "show the current [dir]")
public class Pwd implements Command<CommandInvocation> {

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Override
    public CommandResult execute(CommandInvocation commandInvocation) {
        if (help) {
            commandInvocation.getShell().write(commandInvocation.getHelpInfo("pwd"));
            return CommandResult.SUCCESS;
        }
        commandInvocation.getShell().writeln(commandInvocation.getConfiguration().getAeshContext().getCurrentWorkingDirectory().getAbsolutePath());
        return CommandResult.SUCCESS;
    }
} @CommandDefinition(name = "rm", description = "remove files or directories.")
public class Rm implements Command<CommandInvocation> {

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Option(shortName = 'd', name = "dir", hasValue = false, description = "remove empty directories")
    private boolean dir;

    @Option(shortName = 'i', name = "interactive", hasValue = false, description = "prompt before every removal")
    private boolean interactive;

    @Option(shortName = 'v', name = "verbose", hasValue = false, description = "explain what is being done")
    private boolean verbose;

    @Arguments(description = "files or directories to remove")
    private List<Resource> args;

    @Override
    public CommandResult execute(CommandInvocation ci) throws InterruptedException {
        if (help || args == null || args.isEmpty()) {
            ci.getShell().writeln(ci.getHelpInfo("rm"));
            return CommandResult.SUCCESS;
        }

        Resource currentDir = ci.getConfiguration().getAeshContext().getCurrentWorkingDirectory();
        for (Resource r : args) {
            Resource res = r.resolve(currentDir).get(0);
            if (dir)
                rmDir(res, ci);
            else
                rmFile(res, ci);
        }

        return CommandResult.SUCCESS;
    }

    private void rmFile(Resource r, CommandInvocation ci) throws InterruptedException {
        if (r.exists()) {
            if (r.isLeaf()) {
                if (interactive) {
                    ci.getShell().writeln("remove regular file '" + r.getName() + "' ? (y/n)");
                    if (Key.y.equalTo(ci.input()))
                        r.delete();
                }
                else
                    r.delete();
                if (verbose)
                    ci.getShell().writeln("removed '" + r.getName() + "'");
            }
            else if (r.isDirectory()) {
                ci.getShell().writeln("cannot remove '" + r.getName() + "': Is a directory");
            }
        }
    }

    private void rmDir(Resource r, CommandInvocation ci) throws InterruptedException {
        if (r.exists() && r.isDirectory()) {
            if (interactive) {
                ci.getShell().writeln("remove directory '" + r.getName() + "' ? (y/n)");
                if (Key.y.equalTo(ci.input()))
                    r.delete();
            }
            else
                r.delete();

            if (verbose)
                ci.getShell().writeln("removed directory: '" + r.getName() + "'");
        }
    }
} @CommandDefinition(name = "touch", description = "create and change file timestamps")
public class Touch implements Command<CommandInvocation> {

    @Option(shortName = 'h', name = "help", hasValue = false, description = "display this help and exit")
    private boolean help;

    @Option(shortName = 'a', name = "access time", hasValue = false, description = "change only the access time")
    private boolean changeOnlyAccessTime;

    @Option(shortName = 'm', name = "modification time", hasValue = false, description = "change only the modification time")
    private boolean changeOnlyModificationTime;

    @Option(shortName = 'c', name = "no create", hasValue = false, description = "do not create any files")
    private boolean noCreate;

    @Arguments
    private List<Resource> args;

    @Override
    public CommandResult execute(CommandInvocation ci) {
        if (help || args == null || args.isEmpty()) {
            ci.getShell().writeln(ci.getHelpInfo("touch"));
            return CommandResult.SUCCESS;
        }

        Resource currentDir = ci.getConfiguration().getAeshContext().getCurrentWorkingDirectory();
        for (Resource r : args) {
            Resource res = r.resolve(currentDir).get(0);
            try {
                touch(res, ci);
            }
            catch (IOException e) {
                ci.println("failed to touch: "+e.getMessage());
            }
        }
        return CommandResult.SUCCESS;
    }

    private void touch(Resource r, CommandInvocation ci) throws IOException {
        if (r.exists()) {
            if (changeOnlyAccessTime) {
                r.setLastAccessed(System.currentTimeMillis());
            }

            if (changeOnlyModificationTime) {
                r.setLastModified(System.currentTimeMillis());
            }
        } else {
            if (!noCreate) {
                create(r, ci);
            }
        }
    }

    private void create(Resource r, CommandInvocation ci) throws IOException {
        r.resolve(ci.getConfiguration().getAeshContext().getCurrentWorkingDirectory()).get(0).write(false);
    }
}
import java.io.IOException;
import org.aesh.command.impl.registry.AeshCommandRegistryBuilder;
import org.aesh.command.invocation.CommandInvocation;
import org.aesh.command.parser.CommandLineParserException;
import org.aesh.command.registry.CommandRegistry;
import org.aesh.command.registry.CommandRegistryException;
import org.aesh.command.settings.Settings;
import org.aesh.command.settings.SettingsBuilder;
import org.aesh.extensions.cat.Cat;
import org.aesh.extensions.cd.Cd;
import org.aesh.extensions.clear.Clear;
import org.aesh.extensions.echo.Echo;
import org.aesh.extensions.exit.Exit;
import org.aesh.extensions.grep.Grep;
import org.aesh.extensions.harlem.Harlem;
import org.aesh.extensions.less.Less;
import org.aesh.extensions.ls.Ls;
import org.aesh.extensions.matrix.Matrix;
import org.aesh.extensions.mkdir.Mkdir;
import org.aesh.extensions.more.More;
import org.aesh.extensions.pushdpopd.Popd;
import org.aesh.extensions.pushdpopd.Pushd;
import org.aesh.extensions.pwd.Pwd;
import org.aesh.extensions.rm.Rm;
import org.aesh.extensions.touch.Touch;
import org.aesh.readline.Prompt;
import org.aesh.readline.ReadlineConsole;
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.aesh.extensions.example;



/**
 * @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
 */
public class AeshExampleExtension {

    public static void main(String[] args) throws IOException, CommandRegistryException {

       CommandRegistry registry = AeshCommandRegistryBuilder.builder()
                .command(Exit.class)
                .command(Less.class)
                .command(More.class)
                .command(Harlem.class)
                .command(Clear.class)
                .command(Matrix.class)