Misssed CSV and JSON format for 'acx'; updated 'ac' help. #42

This commit is contained in:
Rob Greene 2022-01-13 22:03:43 -06:00
parent dfc4894a72
commit 676301853c
16 changed files with 186 additions and 475 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<String> globs = new ArrayList<String>();
private List<String> 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<FileColumnHeader> 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<String> data = tuple.fileEntry.getFileColumnData(fileDisplay.format());
for (int i=0; i<tuple.paths.size(); i++) {
System.out.print(" ");
}
for (int d = 0; d < data.size(); d++) {
System.out.printf(fmtSpec.get(d), data.get(d));
}
if (tuple.fileEntry.isDeleted()) {
System.out.print("[deleted]");
}
System.out.println();
}
protected void footer(FormattedDisk disk) {
System.out.printf("%s format; %d bytes free; %d bytes used.\n",
disk.getFormat(),
disk.getFreeSpace(),
disk.getUsedSpace());
}
private List<String> createFormatSpec(List<FileColumnHeader> fileColumnHeaders) {
List<String> 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<Integer,ListingStrategy> constructorFn;
private OutputStrategy(Function<Integer,ListingStrategy> 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<String> fmtSpec;
public FormattedTextListingStrategy(int display) {
super(display);
}
@Override
public void beforeDisk(FormattedDisk disk) {
List<FileColumnHeader> 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<String> data = tuple.fileEntry.getFileColumnData(display);
for (int i=0; i<tuple.paths.size(); i++) {
System.out.print(" ");
}
for (int d = 0; d < data.size(); d++) {
System.out.printf(fmtSpec.get(d), data.get(d));
}
if (tuple.fileEntry.isDeleted()) {
System.out.print("[deleted]");
}
System.out.println();
}
@Override
public void afterDisk(FormattedDisk disk) {
System.out.printf("%s format; %,d bytes free; %,d bytes used.\n",
disk.getFormat(),
disk.getFreeSpace(),
disk.getUsedSpace());
}
private List<String> createFormatSpec(List<FileColumnHeader> fileColumnHeaders) {
List<String> 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) {

View File

@ -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.")

View File

@ -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;

View File

@ -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.")

View File

@ -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).
* <p>
* Sample usage:
* <pre>
* FileStreamer.forDisk(image)
* .ignoreErrors(true)
* .stream()
* .filter(this::fileFilter)
* .forEach(fileHandler);
* </pre>
*
* @author rob
*/
public class FileStreamer {
private static final Consumer<FormattedDisk> 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<FormattedDisk> beforeDisk = NOOP_CONSUMER;
private Consumer<FormattedDisk> afterDisk = NOOP_CONSUMER;
// Filters
private Predicate<FileTuple> filters = this::deletedFileFilter;
private boolean includeDeletedFlag = false;
private List<PathMatcher> 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<String> 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<FormattedDisk> consumer) {
this.beforeDisk = consumer;
return this;
}
public FileStreamer afterDisk(Consumer<FormattedDisk> consumer) {
this.afterDisk = consumer;
return this;
}
public Stream<FileTuple> stream() {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(), 0), false)
.filter(filters);
}
public Iterator<FileTuple> 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<FileTuple> {
private LinkedList<FileTuple> 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<FileTuple> toTupleList(FileTuple tuple) {
List<FileTuple> 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;
}
}
}

View File

@ -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<String> paths;
public final DirectoryEntry directoryEntry;
public final FileEntry fileEntry;
private FileTuple(FormattedDisk formattedDisk,
List<String> 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<String> 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<String>(), (DirectoryEntry)disk, null);
}
}

View File

@ -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<FileTuple> predicate;
private TypeOfFile(Predicate<FileTuple> predicate) {
this.predicate = predicate;
}
}

View File

@ -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<String> 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<String> 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<String> 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<String> actual =
FileStreamer.forDisk("./src/test/resources/disks/MERLIN8PRO1.DSK")
.recursive(false)
.stream()
.map(this::makeFullPath)
.collect(Collectors.toList());
List<String> expected = EXPECTED_MERLIN.stream()
.filter(s -> !s.contains("/"))
.collect(Collectors.toList());
assertEquals(expected, actual);
}
@Test
public void testListUnidos() throws DiskUnrecognizedException, IOException {
List<String> 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());
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -108,6 +108,12 @@ CommandLineHelp = \
-ls <imagename> [<imagename>] list brief directory of image(s).\n\
-l <imagename> [<imagename>] list directory of image(s).\n\
-ll <imagename> [<imagename>] list detailed directory of image(s).\n\
-lsv <imagename> [<imagename>] list in CSV format brief directory of image(s).\n\
-lv <imagename> [<imagename>] list in CSV format directory of image(s).\n\
-llv <imagename> [<imagename>] list in CSV format detailed directory of image(s).\n\
-lsj <imagename> [<imagename>] list in JSON format brief directory of image(s).\n\
-lj <imagename> [<imagename>] list in JSON format directory of image(s).\n\
-llj <imagename> [<imagename>] list in JSON format detailed directory of image(s).\n\
-e <imagename> <filename> [<output>] export file from image to stdout\n or to an output file.\n\
-x <imagename> [<directory>] extract all files from image to directory.\n\
-g <imagename> <filename> [<output>] get raw file from image to stdout\n or to an output file.\n\