From 676301853c94966bae9e2abd40a022fed6cd96ee Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Thu, 13 Jan 2022 22:03:43 -0600 Subject: [PATCH] Misssed CSV and JSON format for 'acx'; updated 'ac' help. #42 --- .../ReadWriteDiskCommandWithGlobOptions.java | 7 +- .../acx/command/CopyFileCommand.java | 6 +- .../acx/command/DeleteCommand.java | 3 +- .../acx/command/ExportCommand.java | 6 +- .../acx/command/ListCommand.java | 160 +++++++++----- .../acx/command/LockCommand.java | 3 +- .../acx/command/RenameFileCommand.java | 7 +- .../acx/command/UnlockCommand.java | 3 +- .../filestreamer/FileStreamer.java | 205 ------------------ .../filestreamer/FileTuple.java | 42 ---- .../filestreamer/TypeOfFile.java | 15 -- .../filestreamer/FileStreamerTest.java | 85 -------- .../filestreamer/FileTupleTest.java | 31 --- .../applecommander/ui/DirectoryLister.java | 67 +++--- .../util/filestreamer/FileStreamer.java | 15 +- .../applecommander/ui/UiBundle.properties | 6 + 16 files changed, 186 insertions(+), 475 deletions(-) delete mode 100644 app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileStreamer.java delete mode 100644 app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileTuple.java delete mode 100644 app/cli-acx/src/main/java/io/github/applecommander/filestreamer/TypeOfFile.java delete mode 100644 app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileStreamerTest.java delete mode 100644 app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileTupleTest.java diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/base/ReadWriteDiskCommandWithGlobOptions.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/base/ReadWriteDiskCommandWithGlobOptions.java index 5fffea4..1097f24 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/base/ReadWriteDiskCommandWithGlobOptions.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/base/ReadWriteDiskCommandWithGlobOptions.java @@ -5,9 +5,10 @@ import java.util.List; import java.util.logging.Logger; import java.util.stream.Collectors; -import io.github.applecommander.filestreamer.FileStreamer; -import io.github.applecommander.filestreamer.FileTuple; -import io.github.applecommander.filestreamer.TypeOfFile; +import com.webcodepro.applecommander.util.filestreamer.FileStreamer; +import com.webcodepro.applecommander.util.filestreamer.FileTuple; +import com.webcodepro.applecommander.util.filestreamer.TypeOfFile; + import picocli.CommandLine.Parameters; public abstract class ReadWriteDiskCommandWithGlobOptions extends ReadWriteDiskCommandOptions { diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CopyFileCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CopyFileCommand.java index c23ea50..7d9d4ec 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CopyFileCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CopyFileCommand.java @@ -7,13 +7,13 @@ import java.util.stream.Collectors; import com.webcodepro.applecommander.storage.Disk; import com.webcodepro.applecommander.storage.DiskException; import com.webcodepro.applecommander.storage.FormattedDisk; +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.ReadWriteDiskCommandOptions; import io.github.applecommander.acx.converter.DiskConverter; import io.github.applecommander.acx.fileutil.FileUtils; -import io.github.applecommander.filestreamer.FileStreamer; -import io.github.applecommander.filestreamer.FileTuple; -import io.github.applecommander.filestreamer.TypeOfFile; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DeleteCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DeleteCommand.java index 26ae2ac..d52c964 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DeleteCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DeleteCommand.java @@ -2,8 +2,9 @@ package io.github.applecommander.acx.command; import java.util.logging.Logger; +import com.webcodepro.applecommander.util.filestreamer.FileTuple; + import io.github.applecommander.acx.base.ReadWriteDiskCommandWithGlobOptions; -import io.github.applecommander.filestreamer.FileTuple; import picocli.CommandLine.Command; import picocli.CommandLine.Option; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ExportCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ExportCommand.java index 7522b43..0037ed9 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ExportCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ExportCommand.java @@ -16,11 +16,11 @@ import com.webcodepro.applecommander.storage.FileEntry; import com.webcodepro.applecommander.storage.FileFilter; import com.webcodepro.applecommander.storage.filters.BinaryFileFilter; import com.webcodepro.applecommander.storage.filters.HexDumpFileFilter; +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 io.github.applecommander.filestreamer.FileStreamer; -import io.github.applecommander.filestreamer.FileTuple; -import io.github.applecommander.filestreamer.TypeOfFile; import io.github.applecommander.filters.AppleSingleFileFilter; import io.github.applecommander.filters.RawFileFilter; import picocli.CommandLine.ArgGroup; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ListCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ListCommand.java index 300e519..4390266 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ListCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ListCommand.java @@ -2,14 +2,18 @@ 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 io.github.applecommander.filestreamer.FileStreamer; -import io.github.applecommander.filestreamer.FileTuple; -import io.github.applecommander.filestreamer.TypeOfFile; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -28,6 +32,9 @@ public class ListCommand extends ReadOnlyDiskImageCommandOptions { @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; @@ -41,67 +48,29 @@ public class ListCommand extends ReadOnlyDiskImageCommandOptions { @Option(names = "--globs", defaultValue = "*", split = ",", description = "File glob(s) to match.") private List globs = new ArrayList(); - private List fmtSpec; - @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(this::header) - .afterDisk(this::footer) + .beforeDisk(listingStrategy::beforeDisk) + .afterDisk(listingStrategy::afterDisk) .stream() - .forEach(this::list); + .forEach(listingStrategy::forEach); + + listingStrategy.last(disk); + return 0; } - protected void header(FormattedDisk disk) { - List headers = disk.getFileColumnHeaders(fileDisplay.format()); - fmtSpec = createFormatSpec(headers); - - System.out.println(); - System.out.printf("File: %s\n", disk.getFilename()); - System.out.printf("Name: %s\n", disk.getDiskName()); - } - - protected void list(FileTuple tuple) { - if (!deletedFlag && tuple.fileEntry.isDeleted()) { - return; - } - - List data = tuple.fileEntry.getFileColumnData(fileDisplay.format()); - 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 FileDisplay { public int format() { if (standardFormat) { @@ -123,6 +92,93 @@ public class ListCommand extends ReadOnlyDiskImageCommandOptions { 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) { diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/LockCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/LockCommand.java index 89577cb..c67a064 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/LockCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/LockCommand.java @@ -2,8 +2,9 @@ package io.github.applecommander.acx.command; import java.util.logging.Logger; +import com.webcodepro.applecommander.util.filestreamer.FileTuple; + import io.github.applecommander.acx.base.ReadWriteDiskCommandWithGlobOptions; -import io.github.applecommander.filestreamer.FileTuple; import picocli.CommandLine.Command; @Command(name = "lock", description = "Lock file(s) on a disk image.") diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/RenameFileCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/RenameFileCommand.java index be99144..f003f49 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/RenameFileCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/RenameFileCommand.java @@ -4,10 +4,11 @@ import java.util.List; import java.util.logging.Logger; import java.util.stream.Collectors; +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.ReadWriteDiskCommandOptions; -import io.github.applecommander.filestreamer.FileStreamer; -import io.github.applecommander.filestreamer.FileTuple; -import io.github.applecommander.filestreamer.TypeOfFile; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/UnlockCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/UnlockCommand.java index 950e330..e813e0c 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/UnlockCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/UnlockCommand.java @@ -2,8 +2,9 @@ package io.github.applecommander.acx.command; import java.util.logging.Logger; +import com.webcodepro.applecommander.util.filestreamer.FileTuple; + import io.github.applecommander.acx.base.ReadWriteDiskCommandWithGlobOptions; -import io.github.applecommander.filestreamer.FileTuple; import picocli.CommandLine.Command; @Command(name = "unlock", description = "Unlock file(s) on a disk image.") diff --git a/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileStreamer.java b/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileStreamer.java deleted file mode 100644 index 089a4a8..0000000 --- a/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileStreamer.java +++ /dev/null @@ -1,205 +0,0 @@ -package io.github.applecommander.filestreamer; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Spliterators; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import com.webcodepro.applecommander.storage.Disk; -import com.webcodepro.applecommander.storage.DiskException; -import com.webcodepro.applecommander.storage.DiskUnrecognizedException; -import com.webcodepro.applecommander.storage.FileEntry; -import com.webcodepro.applecommander.storage.FormattedDisk; - -/** - * FileStreamer is utility class that will (optionally) recurse through all directories and - * feed a Java Stream of useful directory walking detail (disk, directory, file, and the - * textual path to get there). - *

- * Sample usage: - *

- * FileStreamer.forDisk(image)
- *             .ignoreErrors(true)
- *             .stream()
- *             .filter(this::fileFilter)
- *             .forEach(fileHandler);
- * 
- * - * @author rob - */ -public class FileStreamer { - private static final Consumer NOOP_CONSUMER = d -> {}; - - public static FileStreamer forDisk(File file) throws IOException, DiskUnrecognizedException { - return forDisk(file.getPath()); - } - public static FileStreamer forDisk(String fileName) throws IOException, DiskUnrecognizedException { - return new FileStreamer(new Disk(fileName)); - } - public static FileStreamer forDisk(Disk disk) throws DiskUnrecognizedException { - return new FileStreamer(disk); - } - - private FormattedDisk[] formattedDisks = null; - - // Processor flags (used in gathering) - private boolean ignoreErrorsFlag = false; - private boolean recursiveFlag = true; - - // Processor events - private Consumer beforeDisk = NOOP_CONSUMER; - private Consumer afterDisk = NOOP_CONSUMER; - - // Filters - private Predicate filters = this::deletedFileFilter; - private boolean includeDeletedFlag = false; - private List pathMatchers = new ArrayList<>(); - - private FileStreamer(Disk disk) throws DiskUnrecognizedException { - this.formattedDisks = disk.getFormattedDisks(); - } - - public FileStreamer ignoreErrors(boolean flag) { - this.ignoreErrorsFlag = flag; - return this; - } - public FileStreamer recursive(boolean flag) { - this.recursiveFlag = flag; - return this; - } - public FileStreamer matchGlobs(List globs) { - if (globs != null && !globs.isEmpty()) { - FileSystem fs = FileSystems.getDefault(); - for (String glob : globs) { - pathMatchers.add(fs.getPathMatcher("glob:" + glob)); - } - this.filters = filters.and(this::globFilter); - } - return this; - } - public FileStreamer matchGlobs(String... globs) { - return matchGlobs(Arrays.asList(globs)); - } - public FileStreamer includeTypeOfFile(TypeOfFile type) { - this.filters = filters.and(type.predicate); - return this; - } - public FileStreamer includeDeleted(boolean flag) { - this.includeDeletedFlag = flag; - return this; - } - public FileStreamer beforeDisk(Consumer consumer) { - this.beforeDisk = consumer; - return this; - } - public FileStreamer afterDisk(Consumer consumer) { - this.afterDisk = consumer; - return this; - } - - public Stream stream() { - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(), 0), false) - .filter(filters); - } - public Iterator iterator() { - return new FileTupleIterator(); - } - - protected boolean deletedFileFilter(FileTuple tuple) { - return includeDeletedFlag || !tuple.fileEntry.isDeleted(); - } - protected boolean globFilter(FileTuple tuple) { - if (tuple.fileEntry.isDirectory()) { - // If we don't match directories, no files can be listed. - return true; - } - // This may cause issues, but Path is a "real" filesystem construct, so the delimiters - // vary by OS (likely just "/" and "\"). However, Java also erases them to some degree, - // so using "/" (as used in ProDOS) will likely work out. - // Also note that we check the single file "PARMS.S" and full path "SOURCE/PARMS.S" since - // the user might have entered "*.S" or something like "SOURCE/PARMS.S". - FileSystem fs = FileSystems.getDefault(); - Path filePath = Paths.get(tuple.fileEntry.getFilename()); - Path fullPath = Paths.get(String.join(fs.getSeparator(), tuple.paths), - tuple.fileEntry.getFilename()); - for (PathMatcher pathMatcher : pathMatchers) { - if (pathMatcher.matches(filePath) || pathMatcher.matches(fullPath)) return true; - } - return false; - } - - private class FileTupleIterator implements Iterator { - private LinkedList files = new LinkedList<>(); - private FormattedDisk currentDisk; - - private FileTupleIterator() { - for (FormattedDisk formattedDisk : formattedDisks) { - files.addAll(toTupleList(FileTuple.of(formattedDisk))); - } - } - - @Override - public boolean hasNext() { - boolean hasNext = !files.isEmpty(); - if (hasNext) { - FileTuple tuple = files.peek(); - // Was there a disk switch? - if (tuple.formattedDisk != currentDisk) { - if (currentDisk != null) { - afterDisk.accept(currentDisk); - } - currentDisk = tuple.formattedDisk; - beforeDisk.accept(currentDisk); - } - } else { - if (currentDisk != null) { - afterDisk.accept(currentDisk); - } - currentDisk = null; - } - return hasNext; - } - - @Override - public FileTuple next() { - if (hasNext()) { - FileTuple tuple = files.removeFirst(); - if (recursiveFlag && tuple.fileEntry.isDirectory()) { - FileTuple newTuple = tuple.pushd(tuple.fileEntry); - files.addAll(0, toTupleList(newTuple)); - } - return tuple; - } else { - throw new NoSuchElementException(); - } - } - - private List toTupleList(FileTuple tuple) { - List list = new ArrayList<>(); - try { - for (FileEntry fileEntry : tuple.directoryEntry.getFiles()) { - list.add(tuple.of(fileEntry)); - } - } catch (DiskException e) { - if (!ignoreErrorsFlag) { - throw new RuntimeException(e); - } - } - return list; - } - } -} diff --git a/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileTuple.java b/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileTuple.java deleted file mode 100644 index 73cfab9..0000000 --- a/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/FileTuple.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.applecommander.filestreamer; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Logger; - -import com.webcodepro.applecommander.storage.DirectoryEntry; -import com.webcodepro.applecommander.storage.FileEntry; -import com.webcodepro.applecommander.storage.FormattedDisk; - -public class FileTuple { - private static final Logger LOG = Logger.getLogger(FileTuple.class.getName()); - public final FormattedDisk formattedDisk; - public final List paths; - public final DirectoryEntry directoryEntry; - public final FileEntry fileEntry; - - private FileTuple(FormattedDisk formattedDisk, - List paths, - DirectoryEntry directoryEntry, - FileEntry fileEntry) { - this.formattedDisk = formattedDisk; - this.paths = Collections.unmodifiableList(paths); - this.directoryEntry = directoryEntry; - this.fileEntry = fileEntry; - } - - public FileTuple pushd(FileEntry directoryEntry) { - LOG.fine("Adding directory " + directoryEntry.getFilename()); - List newPaths = new ArrayList<>(paths); - newPaths.add(directoryEntry.getFilename()); - return new FileTuple(formattedDisk, newPaths, (DirectoryEntry)directoryEntry, null); - } - public FileTuple of(FileEntry fileEntry) { - return new FileTuple(formattedDisk, paths, directoryEntry, fileEntry); - } - - public static FileTuple of(FormattedDisk disk) { - return new FileTuple(disk, new ArrayList(), (DirectoryEntry)disk, null); - } -} diff --git a/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/TypeOfFile.java b/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/TypeOfFile.java deleted file mode 100644 index ce85c99..0000000 --- a/app/cli-acx/src/main/java/io/github/applecommander/filestreamer/TypeOfFile.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.applecommander.filestreamer; - -import java.util.function.Predicate; - -public enum TypeOfFile { - FILE(tuple -> !tuple.fileEntry.isDirectory()), - DIRECTORY(tuple -> tuple.fileEntry.isDirectory()), - BOTH(tuple -> true); - - public final Predicate predicate; - - private TypeOfFile(Predicate predicate) { - this.predicate = predicate; - } -} \ No newline at end of file diff --git a/app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileStreamerTest.java b/app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileStreamerTest.java deleted file mode 100644 index 36f6ebb..0000000 --- a/app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileStreamerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.applecommander.filestreamer; - -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import com.webcodepro.applecommander.storage.DiskUnrecognizedException; - -import org.junit.Test; - -public class FileStreamerTest { - private static final List EXPECTED_MERLIN = Arrays.asList( - "PRODOS", "MERLIN.SYSTEM", "PARMS", "ED", "ED.16", - "SOURCEROR", "SOURCEROR/OBJ", "SOURCEROR/LABELS", "SOURCEROR/LABELS.S", - "LIBRARY", "LIBRARY/SENDMSG.S", "LIBRARY/PRDEC.S", "LIBRARY/FPMACROS.S", - "LIBRARY/MACROS.S", "LIBRARY/ROCKWELL.S", - "SOURCE", "SOURCE/PARMS.S", "SOURCE/EDMAC.S", "SOURCE/KEYMAC.S", - "SOURCE/PRINTFILER.S", "SOURCE/MAKE.DUMP.S", "SOURCE/CLOCK.S", - "SOURCE/PI.START.S", "SOURCE/PI.MAIN.S", "SOURCE/PI.LOOK.S", - "SOURCE/PI.DIV.S", "SOURCE/PI.ADD.S", "SOURCE/PI.MACS.S", - "SOURCE/PI.NAMES.S", - "UTILITIES", "UTILITIES/REMOVE.ED", "UTILITIES/EDMAC", "UTILITIES/CLOCK.12.ED", - "UTILITIES/XREF", "UTILITIES/XREFA", "UTILITIES/FORMATTER", - "UTILITIES/PRINTFILER", "UTILITIES/MON.65C02", "UTILITIES/MAKE.DUMP", - "UTILITIES/CONV.REL.LNK", "UTILITIES/CONV.LNK.REL", - "UTILITIES/CLR.HI.BIT", "UTILITIES/KEYMAC", - "PI", "PI/NAMES", "PI/START", "PI/MAIN", "PI/LOOK", "PI/DIV", "PI/ADD", "PI/OBJ" - ); - private static final List EXPECTED_UNIDOS = Arrays.asList( - "HELLO", "FORMATTER", "FORMATTER.OBJ", "MFID", "FUD", // Disk #1 - "HELLO", "MFID", "FUD" // Disk #2 - ); - - @Test - public void testRecursiveListMerlin() throws DiskUnrecognizedException, IOException { - List actual = - FileStreamer.forDisk("./src/test/resources/disks/MERLIN8PRO1.DSK") - .recursive(true) - .stream() - .map(this::makeFullPath) - .collect(Collectors.toList()); - - assertEquals(EXPECTED_MERLIN, actual); - } - - @Test - public void testNonRecursiveListMerlin() throws DiskUnrecognizedException, IOException { - List actual = - FileStreamer.forDisk("./src/test/resources/disks/MERLIN8PRO1.DSK") - .recursive(false) - .stream() - .map(this::makeFullPath) - .collect(Collectors.toList()); - - List expected = EXPECTED_MERLIN.stream() - .filter(s -> !s.contains("/")) - .collect(Collectors.toList()); - - assertEquals(expected, actual); - } - - @Test - public void testListUnidos() throws DiskUnrecognizedException, IOException { - List actual = - FileStreamer.forDisk("./src/test/resources/disks/UniDOS_3.3.dsk") - .recursive(true) - .stream() - .map(this::makeFullPath) - .collect(Collectors.toList()); - - assertEquals(EXPECTED_UNIDOS, actual); - } - - - private String makeFullPath(FileTuple tuple) { - if (tuple.paths == null || tuple.paths.isEmpty()) { - return tuple.fileEntry.getFilename(); - } else { - return String.join("/", String.join("/", tuple.paths), tuple.fileEntry.getFilename()); - } - } -} diff --git a/app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileTupleTest.java b/app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileTupleTest.java deleted file mode 100644 index ff34ad0..0000000 --- a/app/cli-acx/src/test/java/io/github/applecommander/filestreamer/FileTupleTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.applecommander.filestreamer; - -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.util.Arrays; - -import com.webcodepro.applecommander.storage.Disk; -import com.webcodepro.applecommander.storage.DiskException; -import com.webcodepro.applecommander.storage.FileEntry; -import com.webcodepro.applecommander.storage.FormattedDisk; - -import org.junit.Test; - -public class FileTupleTest { - @Test - public void test() throws IOException, DiskException { - Disk disk = new Disk("./src/test/resources/disks/MERLIN8PRO1.DSK"); - FormattedDisk formattedDisk = disk.getFormattedDisks()[0]; - FileTuple tuple = FileTuple.of(formattedDisk); - FileEntry sourcerorDir = tuple.formattedDisk.getFile("SOURCEROR"); - tuple = tuple.pushd(sourcerorDir); - FileEntry labelsSource = tuple.directoryEntry.getFiles().get(2); - tuple = tuple.of(labelsSource); - - assertEquals(Arrays.asList("SOURCEROR"), tuple.paths); - assertEquals(formattedDisk, tuple.formattedDisk); - assertEquals(sourcerorDir, tuple.directoryEntry); - assertEquals(labelsSource, tuple.fileEntry); - } -} diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/ui/DirectoryLister.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/ui/DirectoryLister.java index cbf07c0..2e69684 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/ui/DirectoryLister.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/ui/DirectoryLister.java @@ -26,11 +26,7 @@ public class DirectoryLister { return new DirectoryLister(new TextListingStrategy(display)); } public static DirectoryLister csv(int display) { - try { - return new DirectoryLister(new CsvListingStrategy(display)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return new DirectoryLister(new CsvListingStrategy(display)); } public static DirectoryLister json(int display) { return new DirectoryLister(new JsonListingStrategy(display)); @@ -57,32 +53,35 @@ public class DirectoryLister { strategy.last(disk); } - private static abstract class ListingStrategy { + public static abstract class ListingStrategy { protected int display; protected ListingStrategy(int display) { this.display = display; } - protected void first(Disk d) {}; - protected void beforeDisk(FormattedDisk d) {} - protected void afterDisk(FormattedDisk d) {} - protected void forEach(FileTuple f) {} - protected void last(Disk d) {}; + public void first(Disk d) {}; + public void beforeDisk(FormattedDisk d) {} + public void afterDisk(FormattedDisk d) {} + public void forEach(FileTuple f) {} + public void last(Disk d) {}; } - private static class TextListingStrategy extends ListingStrategy { + public static class TextListingStrategy extends ListingStrategy { protected TextListingStrategy(int display) { super(display); } - protected void beforeDisk(FormattedDisk disk) { + @Override + public void beforeDisk(FormattedDisk disk) { System.out.printf("%s %s\n", disk.getFilename(), disk.getDiskName()); } - protected void afterDisk(FormattedDisk disk) { + @Override + public void afterDisk(FormattedDisk disk) { System.out.printf("%s\n\n", textBundle.format("CommandLineStatus", disk.getFormat(), disk.getFreeSpace(), disk.getUsedSpace())); } - protected void forEach(FileTuple tuple) { + @Override + public void forEach(FileTuple tuple) { System.out.printf("%s%s\n", repeat(" ", tuple.paths.size()), String.join(" ", tuple.fileEntry.getFileColumnData(display))); @@ -96,13 +95,18 @@ public class DirectoryLister { } - private static class CsvListingStrategy extends ListingStrategy { + public static class CsvListingStrategy extends ListingStrategy { private CSVPrinter printer; - protected CsvListingStrategy(int display) throws IOException { + public CsvListingStrategy(int display) { super(display); - this.printer = new CSVPrinter(System.out, CSVFormat.DEFAULT); + try { + this.printer = new CSVPrinter(System.out, CSVFormat.DEFAULT); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - protected void beforeDisk(FormattedDisk disk) { + @Override + public void beforeDisk(FormattedDisk disk) { try { printer.printRecord(disk.getFilename(), disk.getDiskName()); printer.printRecord(disk @@ -114,7 +118,8 @@ public class DirectoryLister { throw new RuntimeException(e); } } - protected void afterDisk(FormattedDisk disk) { + @Override + public void afterDisk(FormattedDisk disk) { try { printer.printRecord(disk.getFormat(), disk.getFreeSpace(), disk.getUsedSpace()); printer.println(); @@ -122,7 +127,8 @@ public class DirectoryLister { throw new RuntimeException(e); } } - protected void forEach(FileTuple tuple) { + @Override + public void forEach(FileTuple tuple) { try { printer.printRecord(tuple.fileEntry.getFileColumnData(display)); } catch (IOException e) { @@ -131,16 +137,17 @@ public class DirectoryLister { } } - private static class JsonListingStrategy extends ListingStrategy { + public static class JsonListingStrategy extends ListingStrategy { private JsonObject root; private JsonArray disks; private JsonObject currentDisk; private JsonArray files; private Gson gson = new Gson(); - protected JsonListingStrategy(int display) { + public JsonListingStrategy(int display) { super(display); } - protected void first(Disk disk) { + @Override + public void first(Disk disk) { root = new JsonObject(); root.addProperty("filename", disk.getFilename()); root.addProperty("order", disk.getOrderName()); @@ -148,7 +155,8 @@ public class DirectoryLister { this.disks = new JsonArray(); root.add("disks", disks); } - protected void beforeDisk(FormattedDisk disk) { + @Override + public void beforeDisk(FormattedDisk disk) { currentDisk = new JsonObject(); disks.add(currentDisk); currentDisk.addProperty("diskName", disk.getDiskName()); @@ -160,10 +168,12 @@ public class DirectoryLister { currentDisk.add("files", files); } - protected void afterDisk(FormattedDisk disk) { + @Override + public void afterDisk(FormattedDisk disk) { currentDisk = null; } - protected void forEach(FileTuple tuple) { + @Override + public void forEach(FileTuple tuple) { JsonObject file = new JsonObject(); files.add(file); @@ -173,7 +183,8 @@ public class DirectoryLister { file.addProperty(headers.get(i).getKey(), columns.get(i)); } } - protected void last(Disk disk) { + @Override + public void last(Disk disk) { System.out.println(gson.toJson(root)); } } diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java index f57eaef..e107684 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java @@ -123,10 +123,21 @@ public class FileStreamer { return includeDeletedFlag || !tuple.fileEntry.isDeleted(); } protected boolean globFilter(FileTuple tuple) { + if (recursiveFlag && tuple.fileEntry.isDirectory()) { + // If we don't match directories, no files can be listed. + return true; + } + // This may cause issues, but Path is a "real" filesystem construct, so the delimiters + // vary by OS (likely just "/" and "\"). However, Java also erases them to some degree, + // so using "/" (as used in ProDOS) will likely work out. + // Also note that we check the single file "PARMS.S" and full path "SOURCE/PARMS.S" since + // the user might have entered "*.S" or something like "SOURCE/PARMS.S". FileSystem fs = FileSystems.getDefault(); - Path path = Paths.get(String.join(fs.getSeparator(), tuple.paths), tuple.fileEntry.getFilename()); + Path filePath = Paths.get(tuple.fileEntry.getFilename()); + Path fullPath = Paths.get(String.join(fs.getSeparator(), tuple.paths), + tuple.fileEntry.getFilename()); for (PathMatcher pathMatcher : pathMatchers) { - if (pathMatcher.matches(path)) return true; + if (pathMatcher.matches(filePath) || pathMatcher.matches(fullPath)) return true; } return false; } diff --git a/lib/ac-api/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties b/lib/ac-api/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties index e0634b5..5b367d7 100644 --- a/lib/ac-api/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties +++ b/lib/ac-api/src/main/resources/com/webcodepro/applecommander/ui/UiBundle.properties @@ -108,6 +108,12 @@ CommandLineHelp = \ -ls [] list brief directory of image(s).\n\ -l [] list directory of image(s).\n\ -ll [] list detailed directory of image(s).\n\ + -lsv [] list in CSV format brief directory of image(s).\n\ + -lv [] list in CSV format directory of image(s).\n\ + -llv [] list in CSV format detailed directory of image(s).\n\ + -lsj [] list in JSON format brief directory of image(s).\n\ + -lj [] list in JSON format directory of image(s).\n\ + -llj [] list in JSON format detailed directory of image(s).\n\ -e [] export file from image to stdout\n or to an output file.\n\ -x [] extract all files from image to directory.\n\ -g [] get raw file from image to stdout\n or to an output file.\n\