/* * AppleCommander - An Apple ][ image utility. * Copyright (C) 2019-2022 by Robert Greene and others * robgreene at users.sourceforge.net * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package io.github.applecommander.acx.command; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import com.webcodepro.applecommander.storage.FormattedDisk; import com.webcodepro.applecommander.storage.FormattedDisk.FileColumnHeader; import com.webcodepro.applecommander.ui.DirectoryLister.CsvListingStrategy; import com.webcodepro.applecommander.ui.DirectoryLister.JsonListingStrategy; import com.webcodepro.applecommander.ui.DirectoryLister.ListingStrategy; import com.webcodepro.applecommander.util.filestreamer.FileStreamer; import com.webcodepro.applecommander.util.filestreamer.FileTuple; import com.webcodepro.applecommander.util.filestreamer.TypeOfFile; import io.github.applecommander.acx.base.ReadOnlyDiskImageCommandOptions; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @Command(name = "list", description = "List directory of disk image(s).", aliases = { "ls" }) public class ListCommand extends ReadOnlyDiskImageCommandOptions { @ArgGroup(exclusive = true, multiplicity = "0..1", heading = "%nFile display formatting:%n") private FileDisplay fileDisplay = new FileDisplay(); @Option(names = { "-r", "--recursive"}, description = "Display directory recursively.", negatable = true, defaultValue = "false") private boolean recursiveFlag; @Option(names = { "--deleted" }, description = "Show deleted files.") private boolean deletedFlag; @ArgGroup(exclusive = true, multiplicity = "0..1") private TypeOfFileSelection typeOfFile = new TypeOfFileSelection(); @ArgGroup(exclusive = true, multiplicity = "0..1", heading = "%nOutput format:%n") private OutputType outputType = new OutputType(); @Option(names = "--header", negatable = true, description = "Show header.") private boolean headerFlag = true; @Option(names = "--column", negatable = true, description = "Show column headers.") private boolean columnFlag = true; @Option(names = "--footer", negatable = true, description = "Show footer.") private boolean footerFlag = true; @Option(names = "--globs", defaultValue = "*", split = ",", description = "File glob(s) to match.") private List globs = new ArrayList(); @Override public int handleCommand() throws Exception { int display = fileDisplay.format(); ListingStrategy listingStrategy = outputType.create(display); listingStrategy.first(disk); FileStreamer.forDisk(disk) .ignoreErrors(true) .includeDeleted(deletedFlag) .recursive(recursiveFlag) .includeTypeOfFile(typeOfFile.typeOfFile()) .matchGlobs(globs) .beforeDisk(listingStrategy::beforeDisk) .afterDisk(listingStrategy::afterDisk) .stream() .forEach(listingStrategy::forEach); listingStrategy.last(disk); return 0; } public static class FileDisplay { public int format() { if (standardFormat) { return FormattedDisk.FILE_DISPLAY_STANDARD; } if (longFormat) { return FormattedDisk.FILE_DISPLAY_DETAIL; } return FormattedDisk.FILE_DISPLAY_NATIVE; } @Option(names = { "-n", "--native" }, description = "Use native directory format (default).") private boolean nativeFormat; @Option(names = { "-s", "--short", "--standard" }, description = "Use brief directory format.") private boolean standardFormat; @Option(names = { "-l", "--long", "--detail" }, description = "Use long/detailed directory format.") private boolean longFormat; } public static class OutputType { private OutputStrategy outputStrategy = OutputStrategy.TEXT; public ListingStrategy create(int display) { return outputStrategy.create(display); } private enum OutputStrategy { TEXT(FormattedTextListingStrategy::new), CSV(CsvListingStrategy::new), JSON(JsonListingStrategy::new); private Function constructorFn; private OutputStrategy(Function constructorFn) { this.constructorFn = constructorFn; } public ListingStrategy create(int display) { return constructorFn.apply(display); } }; @Option(names = "--text", description = "Formatted text (default).") public void selectTextOutput(boolean flag) { this.outputStrategy = OutputStrategy.TEXT; } @Option(names = "--json", description = "JSON output.") public void selectJsonOutput(boolean flag) { this.outputStrategy = OutputStrategy.JSON; } @Option(names = "--csv", description = "CSV output.") public void selectCsvOutput(boolean flag) { this.outputStrategy = OutputStrategy.CSV; } } public static class FormattedTextListingStrategy extends ListingStrategy { private List fmtSpec; public FormattedTextListingStrategy(int display) { super(display); } @Override public void beforeDisk(FormattedDisk disk) { List headers = disk.getFileColumnHeaders(display); fmtSpec = createFormatSpec(headers); System.out.println(); System.out.printf("File: %s\n", disk.getFilename()); System.out.printf("Name: %s\n", disk.getDiskName()); } @Override public void forEach(FileTuple tuple) { List data = tuple.fileEntry.getFileColumnData(display); for (int i=0; i createFormatSpec(List fileColumnHeaders) { List fmtSpec = new ArrayList<>(); for (FileColumnHeader h : fileColumnHeaders) { String spec = String.format("%%%s%ds ", h.isRightAlign() ? "" : "-", h.getMaximumWidth()); fmtSpec.add(spec); } return fmtSpec; } } public static class TypeOfFileSelection { public TypeOfFile typeOfFile() { if (filesOnly) { return TypeOfFile.FILE; } if (directoriesOnly) { return TypeOfFile.DIRECTORY; } return TypeOfFile.BOTH; } @Option(names = "--file", description = "Only include files.") private boolean filesOnly; @Option(names = "--directory", description = "Only include directories.") private boolean directoriesOnly; } }