mirror of
https://github.com/AppleCommander/applesingle.git
synced 2024-12-27 21:31:34 +00:00
Adding CLI; fixed a few glitches.
This commit is contained in:
parent
307c5eab3f
commit
70b476b561
125
README.md
125
README.md
@ -3,6 +3,131 @@
|
||||
This project is an off-shoot of AppleCommander's support for the AppleSingle format in that there are multiple
|
||||
Java-based tools that can benefit from a pre-built library to support the AppleSingle format.
|
||||
|
||||
# Java Usage Examples
|
||||
|
||||
## Read AppleSingle
|
||||
|
||||
Use the factory method to...
|
||||
|
||||
Reading from standard input:
|
||||
|
||||
```java
|
||||
AppleSingle as = AppleSingle.read(System.in);
|
||||
```
|
||||
|
||||
Reading from a file:
|
||||
|
||||
```java
|
||||
File file = new File("myfile.as");
|
||||
AppleSingle as = AppleSingle.read(file);
|
||||
```
|
||||
|
||||
The AppleSingle file can be read from an `InputStream`, `File`, `Path`, or just a byte array.
|
||||
|
||||
## Create AppleSingle
|
||||
|
||||
Use the builder to create a new AppleSingle file and then save it...
|
||||
|
||||
```java
|
||||
AppleSingle as = AppleSingle.builder()
|
||||
.dataFork(dataFork)
|
||||
.realName(realName)
|
||||
.build();
|
||||
|
||||
Path file = Paths.get("mynewfile.as");
|
||||
as.save(file);
|
||||
```
|
||||
|
||||
The `save(...)` method can save to a `File`, `Path`, or an `OutputStream`.
|
||||
|
||||
# Command-Line Examples
|
||||
|
||||
For the included command-line utility, we are using `asu` for the name.
|
||||
`as` is the GNU Assembler while `applesingle` is already on Macintoshes.
|
||||
Hopefully that will prevent some confusion!
|
||||
|
||||
Note that all runs are with the `asu` alias defined as `alias asu='java -jar build/libs/applesingle-1.0.0.jar'`
|
||||
(adjust as necessary).
|
||||
|
||||
## Basic usage
|
||||
|
||||
```shell
|
||||
$ asu
|
||||
Usage: asu [-hV] [--debug] [COMMAND]
|
||||
|
||||
AppleSingle utility
|
||||
|
||||
Options:
|
||||
--debug Dump full stack trackes if an error occurs
|
||||
-h, --help Show this help message and exit.
|
||||
-V, --version Print version information and exit.
|
||||
|
||||
Commands:
|
||||
help Displays help information about the specified command
|
||||
info Display information about an AppleSingle file
|
||||
create Create an AppleSingle file
|
||||
extract Extract contents of an AppleSingle file
|
||||
```
|
||||
|
||||
## Subcommand help
|
||||
|
||||
```shell
|
||||
$ asu info --help
|
||||
Usage: asu info [-h] [--stdin] [<file>]
|
||||
|
||||
Display information about an AppleSingle file
|
||||
Please include a file name or indicate stdin should be read, but not both.
|
||||
|
||||
Parameters:
|
||||
[<file>] File to process
|
||||
|
||||
Options:
|
||||
--stdin Read AppleSingle from stdin.
|
||||
-h, --help Show help for subcommand
|
||||
```
|
||||
|
||||
## Info subcommand
|
||||
|
||||
```shell
|
||||
$ asu info src/test/resources/hello.applesingle.bin
|
||||
Real Name: -Unknown-
|
||||
ProDOS info:
|
||||
Access: 0xC3
|
||||
File Type: 0x06
|
||||
Auxtype: 0x0803
|
||||
Data Fork: Present, 2,912 bytes
|
||||
Resource Fork: Not present
|
||||
```
|
||||
|
||||
## Sample runs
|
||||
|
||||
Using pipes to create a text file and display information. Note that the invalid `my-text-file` was changed to `MY.TEXT.FILE`.
|
||||
|
||||
```shell
|
||||
$ echo "Hello World!" | asu create --name my-text-file --stdout --filetype 0x06 --stdin-fork=data --fix-text | asu info --stdin
|
||||
Real Name: MY.TEXT.FILE
|
||||
ProDOS info:
|
||||
Access: 0xC3
|
||||
File Type: 0x06
|
||||
Auxtype: 0x0000
|
||||
Data Fork: Present, 13 bytes
|
||||
Resource Fork: Not present
|
||||
```
|
||||
|
||||
The `--fix-text` file flips the high-bit and translates the newline character.
|
||||
|
||||
```shell
|
||||
$ echo "Hello World!" | asu create --name my-text-file --stdout --filetype 0x06 --stdin-fork=data --fix-text | hexdump -C
|
||||
00000000 00 05 16 00 00 02 00 00 00 00 00 00 00 00 00 00 |................|
|
||||
00000010 00 00 00 00 00 00 00 00 00 03 00 00 00 03 00 00 |................|
|
||||
00000020 00 3e 00 00 00 0c 00 00 00 0b 00 00 00 4a 00 00 |.>...........J..|
|
||||
00000030 00 08 00 00 00 01 00 00 00 52 00 00 00 0d 4d 59 |.........R....MY|
|
||||
00000040 2e 54 45 58 54 2e 46 49 4c 45 00 c3 00 06 00 00 |.TEXT.FILE......|
|
||||
00000050 00 00 c8 e5 ec ec ef a0 d7 ef f2 ec e4 a1 8d |...............|
|
||||
0000005f
|
||||
```
|
||||
(The message is at 0x52 through 0x5e.)
|
||||
|
||||
# Resources
|
||||
|
||||
* [AppleSingle spec](http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf)
|
||||
|
19
build.gradle
19
build.gradle
@ -1,3 +1,7 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.0.2.RELEASE'
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'maven'
|
||||
@ -7,7 +11,18 @@ repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
mainClassName = "io.github.applecommander.applesingle.tools.asu.Main"
|
||||
|
||||
bootJar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Implementation-Title': 'applesingle',
|
||||
'Implementation-Version': "${version} (${new Date().format('yyyy-MM-dd HH:mm')})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'info.picocli:picocli:3.0.2'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
8
gradle.properties
Normal file
8
gradle.properties
Normal file
@ -0,0 +1,8 @@
|
||||
# Universal applesingle version number. Used for:
|
||||
# - Naming JAR file.
|
||||
# - The build will insert this into a file that is read at run time as well.
|
||||
version=1.0.0
|
||||
|
||||
# Maven Central Repository G and A of GAV coordinate. :-)
|
||||
group=net.sf.applecommander
|
||||
archivesBaseName=AppleCommander
|
@ -114,22 +114,22 @@ public class AppleSingle {
|
||||
}
|
||||
|
||||
public void save(OutputStream outputStream) throws IOException {
|
||||
// Support real name, prodos file info, resource fork (if present), data fork (assumed)
|
||||
final boolean hasResourceFork = resourceFork == null ? false : true;
|
||||
final int entries = 3 + (hasResourceFork ? 1 : 0);
|
||||
final boolean hasRealName = realName == null ? false : true;
|
||||
final int entries = 2 + (hasRealName ? 1 : 0) + (hasResourceFork ? 1 : 0);
|
||||
|
||||
int realNameOffset = 26 + (12 * entries);;
|
||||
int prodosFileInfoOffset = realNameOffset + realName.length();
|
||||
int realNameOffset = 26 + (12 * entries);
|
||||
int prodosFileInfoOffset = realNameOffset + (hasRealName ? realName.length() : 0);
|
||||
int resourceForkOffset = prodosFileInfoOffset + 8;
|
||||
int dataForkOffset = resourceForkOffset + (hasResourceFork ? resourceFork.length : 0);
|
||||
|
||||
writeFileHeader(outputStream, entries);
|
||||
writeHeader(outputStream, 3, realNameOffset, realName.length());
|
||||
if (hasRealName) writeHeader(outputStream, 3, realNameOffset, realName.length());
|
||||
writeHeader(outputStream, 11, prodosFileInfoOffset, 8);
|
||||
if (hasResourceFork) writeHeader(outputStream, 2, resourceForkOffset, resourceFork.length);
|
||||
writeHeader(outputStream, 1, dataForkOffset, dataFork.length);
|
||||
|
||||
writeRealName(outputStream);
|
||||
if (hasRealName) writeRealName(outputStream);
|
||||
writeProdosFileInfo(outputStream);
|
||||
if (hasResourceFork) writeResourceFork(outputStream);
|
||||
writeDataFork(outputStream);
|
||||
@ -168,7 +168,7 @@ public class AppleSingle {
|
||||
ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putShort((short)prodosFileInfo.access);
|
||||
buf.putShort((short)prodosFileInfo.fileType);
|
||||
buf.putInt(prodosFileInfo.fileType);
|
||||
buf.putInt(prodosFileInfo.auxType);
|
||||
outputStream.write(buf.array());
|
||||
}
|
||||
private void writeResourceFork(OutputStream outputStream) throws IOException {
|
||||
@ -179,22 +179,15 @@ public class AppleSingle {
|
||||
}
|
||||
|
||||
public static AppleSingle read(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
while (true) {
|
||||
byte[] buf = new byte[1024];
|
||||
int len = inputStream.read(buf);
|
||||
if (len == -1) break;
|
||||
outputStream.write(buf, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
return read(outputStream.toByteArray());
|
||||
Objects.requireNonNull(inputStream, "Please supply an input stream");
|
||||
return read(AppleSingle.toByteArray(inputStream));
|
||||
}
|
||||
public static AppleSingle read(File file) throws IOException {
|
||||
Objects.requireNonNull(file);
|
||||
Objects.requireNonNull(file, "Please supply a file");
|
||||
return read(file.toPath());
|
||||
}
|
||||
public static AppleSingle read(Path path) throws IOException {
|
||||
Objects.requireNonNull(path);
|
||||
Objects.requireNonNull(path, "Please supply a file");
|
||||
return new AppleSingle(Files.readAllBytes(path));
|
||||
}
|
||||
public static AppleSingle read(byte[] data) throws IOException {
|
||||
@ -208,9 +201,22 @@ public class AppleSingle {
|
||||
public static class Builder {
|
||||
private AppleSingle as = new AppleSingle();
|
||||
public Builder realName(String realName) {
|
||||
as.realName = realName;
|
||||
if (!Character.isAlphabetic(realName.charAt(0))) {
|
||||
throw new IllegalArgumentException("ProDOS file names must begin with a letter");
|
||||
}
|
||||
as.realName = realName.chars()
|
||||
.map(this::sanitize)
|
||||
.limit(15)
|
||||
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||
.toString();
|
||||
return this;
|
||||
}
|
||||
private int sanitize(int ch) {
|
||||
if (Character.isAlphabetic(ch) || Character.isDigit(ch)) {
|
||||
return Character.toUpperCase(ch);
|
||||
}
|
||||
return '.';
|
||||
}
|
||||
public Builder dataFork(byte[] dataFork) {
|
||||
as.dataFork = dataFork;
|
||||
return this;
|
||||
@ -235,4 +241,17 @@ public class AppleSingle {
|
||||
return as;
|
||||
}
|
||||
}
|
||||
|
||||
/** Utility method to read all bytes from an InputStream. May move if more utility methods appear. */
|
||||
public static byte[] toByteArray(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
while (true) {
|
||||
byte[] buf = new byte[1024];
|
||||
int len = inputStream.read(buf);
|
||||
if (len == -1) break;
|
||||
outputStream.write(buf, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public class ProdosFileInfo {
|
||||
int auxType;
|
||||
|
||||
public static ProdosFileInfo standardBIN() {
|
||||
return new ProdosFileInfo(0xc3, 0x04, 0x000);
|
||||
return new ProdosFileInfo(0xc3, 0x06, 0x0000);
|
||||
}
|
||||
|
||||
public ProdosFileInfo(int access, int fileType, int auxType) {
|
||||
|
@ -1 +0,0 @@
|
||||
package io.github.applecommander.applesingle;
|
@ -0,0 +1,135 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
/**
|
||||
* Supports creation of AppleSingle archives.
|
||||
*/
|
||||
@Command(name = "create", description = { "Create an AppleSingle file" },
|
||||
parameterListHeading = "%nParameters:%n",
|
||||
descriptionHeading = "%n",
|
||||
optionListHeading = "%nOptions:%n")
|
||||
public class CreateCommand implements Callable<Void> {
|
||||
@Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true)
|
||||
private boolean helpFlag;
|
||||
|
||||
@Option(names = "--stdout", description = "Write AppleSingle file to stdout")
|
||||
private boolean stdoutFlag;
|
||||
|
||||
@Option(names = "--stdin-fork", description = "Read fork from stdin (specify data or resource)")
|
||||
private ForkType stdinForkType;
|
||||
|
||||
@Option(names = "--fix-text", description = "Set the high bit and fix line endings")
|
||||
private boolean fixTextFlag;
|
||||
|
||||
@Option(names = "--data-fork", description = "Read data fork from file")
|
||||
private Path dataForkFile;
|
||||
|
||||
@Option(names = "--resource-fork", description = "Read resource fork from file")
|
||||
private Path resourceForkFile;
|
||||
|
||||
@Option(names = "--name", description = "Set the filename (defaults to name of data fork, if supplied)")
|
||||
private String realName;
|
||||
|
||||
@Option(names = "--access", description = "Set the ProDOS access flags", converter = IntegerTypeConverter.class)
|
||||
private Integer access;
|
||||
|
||||
@Option(names = "--filetype", description = "Set the ProDOS file type", converter = IntegerTypeConverter.class)
|
||||
private Integer filetype;
|
||||
|
||||
@Option(names = "--auxtype", description = "Set the ProDOS auxtype", converter = IntegerTypeConverter.class)
|
||||
private Integer auxtype;
|
||||
|
||||
@Parameters(arity = "0..1", description = "AppleSingle file to create")
|
||||
private Path file;
|
||||
|
||||
@Override
|
||||
public Void call() throws IOException {
|
||||
validateArguments();
|
||||
|
||||
byte[] dataFork = prepDataFork();
|
||||
byte[] resourceFork = prepResourceFork();
|
||||
|
||||
AppleSingle applesingle = buildAppleSingle(dataFork, resourceFork);
|
||||
writeAppleSingle(applesingle);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void validateArguments() throws IOException {
|
||||
if ((stdoutFlag && file != null) || (!stdoutFlag && file == null)) {
|
||||
throw new IOException("Please choose one of stdout or output file");
|
||||
}
|
||||
if ((dataForkFile != null && stdinForkType == ForkType.data)
|
||||
|| (resourceForkFile != null && stdinForkType == ForkType.resource)) {
|
||||
throw new IOException("Stdin only supports one type of fork for input");
|
||||
}
|
||||
if (dataForkFile == null && resourceForkFile == null && stdinForkType == null) {
|
||||
throw new IOException("Please select at least one fork type");
|
||||
}
|
||||
if (stdinForkType == ForkType.both) {
|
||||
throw new IOException("Unable to read two forks from stdin");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] prepDataFork() throws IOException {
|
||||
byte[] dataFork = null;
|
||||
if (stdinForkType == ForkType.data) {
|
||||
dataFork = AppleSingle.toByteArray(System.in);
|
||||
} else if (dataForkFile != null) {
|
||||
dataFork = Files.readAllBytes(dataForkFile);
|
||||
}
|
||||
|
||||
if (fixTextFlag && dataFork != null) {
|
||||
for (int i=0; i<dataFork.length; i++) {
|
||||
if (dataFork[i] == '\n') dataFork[i] = 0x0d;
|
||||
dataFork[i] = (byte)(dataFork[i] | 0x80);
|
||||
}
|
||||
}
|
||||
return dataFork;
|
||||
}
|
||||
|
||||
public byte[] prepResourceFork() throws IOException {
|
||||
byte[] resourceFork = null;
|
||||
if (stdinForkType == ForkType.resource) {
|
||||
resourceFork = AppleSingle.toByteArray(System.in);
|
||||
} else if (resourceForkFile != null) {
|
||||
resourceFork = Files.readAllBytes(resourceForkFile);
|
||||
}
|
||||
return resourceFork;
|
||||
}
|
||||
|
||||
public AppleSingle buildAppleSingle(byte[] dataFork, byte[] resourceFork) {
|
||||
AppleSingle.Builder builder = AppleSingle.builder();
|
||||
if (realName != null) {
|
||||
builder.realName(realName);
|
||||
} else if (dataForkFile != null) {
|
||||
String name = dataForkFile.getFileName().toString();
|
||||
builder.realName(name);
|
||||
}
|
||||
if (access != null) builder.access(access.intValue());
|
||||
if (filetype != null) builder.fileType(filetype.intValue());
|
||||
if (auxtype != null) builder.auxType(auxtype.intValue());
|
||||
if (dataFork != null) builder.dataFork(dataFork);
|
||||
if (resourceFork != null) builder.resourceFork(resourceFork);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void writeAppleSingle(AppleSingle applesingle) throws IOException {
|
||||
if (stdoutFlag) {
|
||||
applesingle.save(System.out);
|
||||
} else {
|
||||
applesingle.save(file);
|
||||
System.out.printf("Saved to '%s'.\n", file);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Help.Visibility;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
/**
|
||||
* Supports extracting components of an AppleSingle archive.
|
||||
*/
|
||||
@Command(name = "extract", description = { "Extract contents of an AppleSingle file" },
|
||||
parameterListHeading = "%nParameters:%n",
|
||||
descriptionHeading = "%n",
|
||||
optionListHeading = "%nOptions:%n")
|
||||
public class ExtractCommand implements Callable<Void> {
|
||||
@Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true)
|
||||
private boolean helpFlag;
|
||||
|
||||
@Option(names = "--stdout", description = "Write selected fork to stdout")
|
||||
private boolean stdoutFlag;
|
||||
|
||||
@Option(names = "--stdin", description = "Read AppleSingle from stdin")
|
||||
private boolean stdinFlag;
|
||||
|
||||
@Option(names = "--fix-text", description = "Clear the high bit and fix line endings")
|
||||
private boolean fixTextFlag;
|
||||
|
||||
@Option(names = "--fork", description = "Extract which fork type (specify data, resource, or both",
|
||||
showDefaultValue = Visibility.ALWAYS)
|
||||
private ForkType forkType = ForkType.data;
|
||||
|
||||
@Option(names = { "-o", "--output" }, description = "Write fork(s) to base filename")
|
||||
private String baseFilename;
|
||||
|
||||
@Parameters(arity = "0..1", description = "File to process")
|
||||
private Path file;
|
||||
|
||||
@Override
|
||||
public Void call() throws IOException {
|
||||
validateArguments();
|
||||
|
||||
AppleSingle applesingle = stdinFlag ? AppleSingle.read(System.in) : AppleSingle.read(file);
|
||||
if (!stdoutFlag && baseFilename == null && applesingle.getRealName() == null) {
|
||||
throw new IOException("Please include an output base filename; this AppleSingle file does not contain a name");
|
||||
}
|
||||
if (baseFilename == null) {
|
||||
baseFilename = applesingle.getRealName();
|
||||
}
|
||||
|
||||
writeFork(ForkType.data, applesingle.getDataFork());
|
||||
writeFork(ForkType.resource, applesingle.getResourceFork());
|
||||
return null;
|
||||
}
|
||||
|
||||
public void validateArguments() throws IOException {
|
||||
if (stdoutFlag && baseFilename != null) {
|
||||
throw new IOException("Please choose one of stdout or output file");
|
||||
}
|
||||
if (stdoutFlag && forkType == ForkType.both) {
|
||||
throw new IOException("Stdout only supports one type of fork for output");
|
||||
}
|
||||
if ((stdinFlag && file != null) || (!stdinFlag && file == null)) {
|
||||
throw new IOException("Please select ONE of stdin or file");
|
||||
}
|
||||
}
|
||||
|
||||
public void writeFork(ForkType forkType, byte[] data) throws IOException {
|
||||
if (this.forkType != forkType && this.forkType != ForkType.both) return;
|
||||
|
||||
if (data == null || data.length == 0) {
|
||||
throw new IOException(String.format("There is no data in the %s fork, aborting", forkType));
|
||||
}
|
||||
|
||||
if (fixTextFlag) {
|
||||
for (int i=0; i<data.length; i++) {
|
||||
data[i] = (byte)(data[i] & 0x7f);
|
||||
if (data[i] == 0x0d) data[i] = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (baseFilename != null) {
|
||||
String targetFilename = String.format("%s.%s", baseFilename, forkType.name());
|
||||
System.out.printf("Writing %s fork to file '%s'...\n", forkType.name(), targetFilename);
|
||||
Path path = Paths.get(targetFilename);
|
||||
Files.write(path, data);
|
||||
}
|
||||
if (stdoutFlag) {
|
||||
System.out.write(data);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
/**
|
||||
* Basic listing of fork types used in the command line tools.
|
||||
*/
|
||||
public enum ForkType {
|
||||
data, resource, both
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle;
|
||||
import io.github.applecommander.applesingle.ProdosFileInfo;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
/**
|
||||
* Display basic information from an AppleSingle archive.
|
||||
*/
|
||||
@Command(name = "info", description = { "Display information about an AppleSingle file",
|
||||
"Please include a file name or indicate stdin should be read, but not both." },
|
||||
parameterListHeading = "%nParameters:%n",
|
||||
descriptionHeading = "%n",
|
||||
optionListHeading = "%nOptions:%n")
|
||||
public class InfoCommand implements Callable<Void> {
|
||||
@Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true)
|
||||
private boolean helpFlag;
|
||||
|
||||
@Option(names = "--stdin", description = "Read AppleSingle from stdin.")
|
||||
private boolean stdinFlag;
|
||||
|
||||
@Parameters(arity = "0..1", description = "File to process")
|
||||
private File file;
|
||||
|
||||
@Override
|
||||
public Void call() throws IOException {
|
||||
AppleSingle applesingle = stdinFlag ? AppleSingle.read(System.in) : AppleSingle.read(file);
|
||||
System.out.printf("Real Name: %s\n", Optional.ofNullable(applesingle.getRealName()).orElse("-Unknown-"));
|
||||
System.out.printf("ProDOS info:\n");
|
||||
if (applesingle.getProdosFileInfo() == null) {
|
||||
System.out.println(" Not supplied.");
|
||||
} else {
|
||||
ProdosFileInfo prodosFileInfo = applesingle.getProdosFileInfo();
|
||||
System.out.printf(" Access: 0x%02X\n", prodosFileInfo.getAccess());
|
||||
System.out.printf(" File Type: 0x%02X\n", prodosFileInfo.getFileType());
|
||||
System.out.printf(" Auxtype: 0x%04X\n", prodosFileInfo.getAuxType());
|
||||
}
|
||||
System.out.printf("Data Fork: Present, %,d bytes\n", applesingle.getDataFork().length);
|
||||
System.out.printf("Resource Fork: %s\n",
|
||||
Optional.ofNullable(applesingle.getResourceFork())
|
||||
.map(d -> String.format("Present, %,d bytes", d.length))
|
||||
.orElse("Not present"));
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import picocli.CommandLine.ITypeConverter;
|
||||
|
||||
/** Add support for "$801" and "0x801" instead of just decimal like 2049. */
|
||||
public class IntegerTypeConverter implements ITypeConverter<Integer> {
|
||||
@Override
|
||||
public Integer convert(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
} else if (value.startsWith("$")) {
|
||||
return Integer.valueOf(value.substring(1), 16);
|
||||
} else if (value.startsWith("0x") || value.startsWith("0X")) {
|
||||
return Integer.valueOf(value.substring(2), 16);
|
||||
} else {
|
||||
return Integer.valueOf(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.HelpCommand;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
/**
|
||||
* Primary entry point into the AppleSingle utility.
|
||||
*/
|
||||
@Command(name = "asu", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class,
|
||||
descriptionHeading = "%n",
|
||||
commandListHeading = "%nCommands:%n",
|
||||
optionListHeading = "%nOptions:%n",
|
||||
description = "AppleSingle utility",
|
||||
subcommands = { HelpCommand.class, InfoCommand.class, CreateCommand.class, ExtractCommand.class })
|
||||
public class Main implements Runnable {
|
||||
@Option(names = "--debug", description = "Dump full stack trackes if an error occurs")
|
||||
private static boolean debugFlag;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
CommandLine.run(new Main(), args);
|
||||
} catch (Throwable t) {
|
||||
if (Main.debugFlag) {
|
||||
t.printStackTrace(System.err);
|
||||
} else {
|
||||
String message = t.getMessage();
|
||||
while (t != null) {
|
||||
message = t.getMessage();
|
||||
t = t.getCause();
|
||||
}
|
||||
System.err.printf("Error: %s\n", Optional.ofNullable(message).orElse("An error occurred."));
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CommandLine.usage(this, System.out);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import picocli.CommandLine.IVersionProvider;
|
||||
|
||||
/** Display version information. Note that this is dependent on the Spring Boot Gradle plugin configuration. */
|
||||
public class VersionProvider implements IVersionProvider {
|
||||
public String[] getVersion() {
|
||||
return new String[] { Main.class.getPackage().getImplementationVersion() };
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ public class AppleSingleTest {
|
||||
.realName(realName)
|
||||
.build();
|
||||
assertNotNull(createdAS);
|
||||
assertEquals(realName, createdAS.getRealName());
|
||||
assertEquals(realName.toUpperCase(), createdAS.getRealName());
|
||||
assertArrayEquals(dataFork, createdAS.getDataFork());
|
||||
assertNull(createdAS.getResourceFork());
|
||||
assertNotNull(createdAS.getProdosFileInfo());
|
||||
@ -50,9 +50,26 @@ public class AppleSingleTest {
|
||||
|
||||
AppleSingle readAS = AppleSingle.read(actualBytes.toByteArray());
|
||||
assertNotNull(readAS);
|
||||
assertEquals(realName, readAS.getRealName());
|
||||
assertEquals(realName.toUpperCase(), readAS.getRealName());
|
||||
assertArrayEquals(dataFork, readAS.getDataFork());
|
||||
assertNull(readAS.getResourceFork());
|
||||
assertNotNull(readAS.getProdosFileInfo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProdosFileNameLengthRequirements() {
|
||||
AppleSingle as = AppleSingle.builder().realName("superlongnamethatneedstobetruncated").build();
|
||||
assertEquals(15, as.getRealName().length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProdosFileNameCharacterRequirements() {
|
||||
AppleSingle as = AppleSingle.builder().realName("bad-~@").build();
|
||||
assertEquals("BAD...", as.getRealName());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testProdosFileNameFirstCharacter() {
|
||||
AppleSingle.builder().realName("1st-file").build();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user