mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2025-01-02 19:29:17 +00:00
Misssed CSV and JSON format for 'acx'; updated 'ac' help. #42
This commit is contained in:
parent
dfc4894a72
commit
676301853c
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
@ -29,6 +33,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) {
|
||||
|
@ -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.")
|
||||
|
@ -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;
|
||||
|
@ -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.")
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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\
|
||||
|
Loading…
Reference in New Issue
Block a user