Adding a -ptx flag to load a text file with high bit cleared. #33

This commit is contained in:
Rob Greene 2019-09-29 18:11:41 -05:00
parent aa3e8ae22f
commit c26a8b0844
4 changed files with 160 additions and 47 deletions

View File

@ -136,7 +136,9 @@ public class ac {
putFile(args[1], new Name(args[2]), args[3],
(args.length > 4 ? args[4] : "0x2000"));
} else if ("-pt".equalsIgnoreCase(args[0])) {
putTxtFile(args[1], new Name(args[2]));
putTxtFileSetHighBit(args[1], new Name(args[2]));
} else if ("-ptx".equalsIgnoreCase(args[0])) {
putTxtFileClearHighBit(args[1], new Name(args[2]));
} else if ("-d".equalsIgnoreCase(args[0])) { //$NON-NLS-1$
deleteFile(args[1], args[2]);
} else if ("-k".equalsIgnoreCase(args[0])) { //$NON-NLS-1$
@ -270,10 +272,20 @@ public class ac {
* Put <stdin&gt. as an Apple text file into the file named
* fileName on the disk named imageName.
*/
static void putTxtFile(String imageName, Name name) throws IOException, DiskException {
static void putTxtFileSetHighBit(String imageName, Name name) throws IOException, DiskException {
// Order on the stream is important to ensure the translated newlines have the high bit done appropriately
putFile(imageName, name, "TXT", "0", TranslatorStream.builder(System.in).lfToCr().setHighBit().get());
}
/**
* Put <stdin&gt. as an Apple text file into the file named
* fileName on the disk named imageName.
*/
static void putTxtFileClearHighBit(String imageName, Name name) throws IOException, DiskException {
// Order on the stream is important to ensure the translated newlines have the high bit done appropriately
putFile(imageName, name, "TXT", "0", TranslatorStream.builder(System.in).lfToCr().clearHighBit().get());
}
/**
* Put InputStream into the file named fileName on the disk named imageName;
* Note: only volume level supported; input size unlimited.

View File

@ -17,12 +17,19 @@ public class TranslatorStream extends InputStream {
@Override
public int read() throws IOException {
return fn.apply(sourceStream.read());
int data = sourceStream.read();
if (data == -1) {
return -1;
}
return fn.apply(data);
}
private int setHighBit(int value) {
return value | 0x80;
}
private int clearHighBit(int value) {
return value & 0x7f;
}
private int lfToCr(int value) {
if (value == '\r') {
try {
@ -58,6 +65,9 @@ public class TranslatorStream extends InputStream {
public Builder setHighBit() {
return fn(stream::setHighBit);
}
public Builder clearHighBit() {
return fn(stream::clearHighBit);
}
public Builder lfToCr() {
return fn(stream::lfToCr);
}

View File

@ -103,29 +103,31 @@ CommandLineErrorMessage = Error: {0}
CommandLineNoMatchMessage = {0}: No match.
CommandLineStatus = {0} format; {1} bytes free; {2} bytes used.
CommandLineHelp = \
CommandLineHelp = AppleCommander command line options [{0}]:\n\
-i <imagename> [<imagename>] display information about image(s).\n\
-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\
-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\
-p <imagename> <filename> <type> [[$|0x]<addr>] put stdin\n in filename on image, using file type and address [0x2000].\n\
-pt <imagename> <filename> put stdin in filename on image\n defaulting to TXT file type, setting high bit on and replacing\n newline characters with $8D.\n\
-d <imagename> <filename> delete file from image.\n\
-k <imagename> <filename> lock file on image.\n\
-u <imagename> <filename> unlock file on image.\n\
-n <imagename> <volname> change volume name (ProDOS or Pascal).\n\
-dos <imagename> <filename> <type> put stdin with DOS header\n in filename on image, using file type and address from header.\n\
-as <imagename> [<filename>] put stdin with AppleSingle format\n in filename on image, using file type, address, and (optionally) name\n from the AppleSingle file.\n\
-geos <imagename> interpret stdin as a GEOS conversion file and\n place it on image (ProDOS only).\n\
-dos140 <imagename> create a 140K DOS 3.3 image.\n-pro140 <imagename> <volname> create a 140K ProDOS image.\n\
-pro800 <imagename> <volname> create an 800K ProDOS image.\n\
-pas140 <imagename> <volname> create a 140K Pascal image.\n\
-pas800 <imagename> <volname> create an 800K Pascal image.\n\
AppleCommander command line options [{0}]:\n\
-i <imagename> [<imagename>] display information about image(s).\n\
-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\
-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\
-p <imagename> <filename> <type> [[$|0x]<addr>] put stdin\n in filename on image, using file type and address [0x2000].\n\
-pt <imagename> <filename> put stdin in filename on image\n defaulting to TXT file type, setting high bit on and replacing\n newline characters with $8D.\n\
-ptx <imagename> <filename> put stdin in filename on image\n defaulting to TXT file type, clearing high bit and replacing\n newline characters with $0D.\n\
-d <imagename> <filename> delete file from image.\n\
-k <imagename> <filename> lock file on image.\n\
-u <imagename> <filename> unlock file on image.\n\
-n <imagename> <volname> change volume name (ProDOS or Pascal).\n\
-dos <imagename> <filename> <type> put stdin with DOS header\n in filename on image, using file type and address from header.\n\
-as <imagename> [<filename>] put stdin with AppleSingle format\n in filename on image, using file type, address, and (optionally) name\n from the AppleSingle file.\n\
-geos <imagename> interpret stdin as a GEOS conversion file and\n place it on image (ProDOS only).\n\
-dos140 <imagename> create a 140K DOS 3.3 image.\n\
-pro140 <imagename> <volname>\n create a 140K ProDOS image.\n\
-pro800 <imagename> <volname> create an 800K ProDOS image.\n\
-pas140 <imagename> <volname> create a 140K Pascal image.\n\
-pas800 <imagename> <volname> create an 800K Pascal image.\n\
-convert <filename> <imagename> [<sizeblocks>] uncompress a ShrinkIt or Binary\n II file; or convert a DiskCopy 4.2 image into a ProDOS disk image.\n\
-bas <imagename> <filename> import an AppleSoft basic file from text\n back to it's tokenized format.
-bas <imagename> <filename> import an AppleSoft basic file from text\n back to it's tokenized format.
CommandLineSDKReadOnly = SDK, SHK, and DC42 files are read-only. Use the convert option on them first.
CommandLineDC42Bad = Unable to interpret this DiskCopy 42 image.

View File

@ -4,35 +4,37 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Supplier;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class TranslatorStreamTest {
@Test
public void testUnixLineEndings() throws IOException {
byte[] source = "Hello\nWorld!\n".getBytes();
testAgainstExpected(source);
@Parameters(name = "{index}: {0}")
public static Collection<TestData> data() {
return Arrays.asList(
TestData.builder().unixLineEndings().highBitSet().build(),
TestData.builder().dosLineEndings().highBitSet().build(),
TestData.builder().appleLineEndings().highBitSet().build(),
TestData.builder().unixLineEndings().highBitClear().build(),
TestData.builder().dosLineEndings().highBitClear().build(),
TestData.builder().appleLineEndings().highBitClear().build()
);
}
@Parameter
public TestData testData;
@Test
public void testDosLineEndings() throws IOException {
byte[] source = "Hello\r\nWorld!\r\n".getBytes();
testAgainstExpected(source);
}
@Test
public void testAppleLineEndings() throws IOException {
byte[] source = "Hello\rWorld!\r".getBytes();
testAgainstExpected(source);
}
private void testAgainstExpected(final byte[] source) throws IOException {
final byte[] expected = { (byte)0xc8, (byte)0xe5, (byte)0xec, (byte)0xec,
(byte)0xef, (byte)0x8d, (byte)0xd7, (byte)0xef, (byte)0xf2,
(byte)0xec, (byte)0xe4, (byte)0xa1, (byte)0x8d };
InputStream is = TranslatorStream.builder(new ByteArrayInputStream(source)).lfToCr().setHighBit().get();
public void test() throws IOException {
InputStream is = testData.getSourceStream();
byte[] actual = null;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
@ -40,6 +42,93 @@ public class TranslatorStreamTest {
actual = os.toByteArray();
}
Assert.assertArrayEquals(expected, actual);
Assert.assertArrayEquals(testData.getExpected(), actual);
}
public static class TestData {
private String name;
private byte[] expected;
private InputStream sourceStream;
public TestData(String name, byte[] expected, InputStream sourceStream) {
this.name = name;
this.expected = expected;
this.sourceStream = sourceStream;
}
public byte[] getExpected() {
return expected;
}
public InputStream getSourceStream() {
return sourceStream;
}
@Override
public String toString() {
return name;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String expectedDescription;
private byte[] expected;
private String sourceDescription;
private byte[] source;
private Supplier<TranslatorStream> configurer;
public Builder unixLineEndings() {
this.sourceDescription = "unix line endings";
this.source = "Hello\nWorld!\n".getBytes();
return this;
}
public Builder dosLineEndings() {
this.sourceDescription = "dos line endings";
this.source = "Hello\r\nWorld!\r\n".getBytes();
return this;
}
public Builder appleLineEndings() {
this.sourceDescription = "apple line endings";
this.source = "Hello\rWorld!\r".getBytes();
return this;
}
/** "Hello^MWorld!^M" with high bit set. */
public Builder highBitSet() {
this.expectedDescription = "high bit set";
this.expected = new byte[] {
(byte)0xc8, (byte)0xe5, (byte)0xec, (byte)0xec,
(byte)0xef, (byte)0x8d, (byte)0xd7, (byte)0xef, (byte)0xf2,
(byte)0xec, (byte)0xe4, (byte)0xa1, (byte)0x8d
};
this.configurer = this::highBitSetConfigurer;
return this;
}
private TranslatorStream highBitSetConfigurer() {
return TranslatorStream.builder(new ByteArrayInputStream(source)).lfToCr().setHighBit().get();
}
/** "Hello^MWorld!^M" with high bit clear. */
public Builder highBitClear() {
this.expectedDescription = "high bit clear";
this.expected = new byte[] {
0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x0d, 0x57, 0x6f, 0x72,
0x6c, 0x64, 0x21, 0x0d
};
this.configurer = this::highBitClearConfigurer;
return this;
}
private TranslatorStream highBitClearConfigurer() {
return TranslatorStream.builder(new ByteArrayInputStream(source)).lfToCr().clearHighBit().get();
}
public TestData build() {
if (this.expected == null || this.expectedDescription == null
|| this.source == null || this.sourceDescription == null
|| this.configurer == null) {
throw new RuntimeException("Not all variables are set!");
}
String name = String.format("%s with %s", this.sourceDescription, this.expectedDescription);
return new TestData(name, this.expected, this.configurer.get());
}
}
}
}