Extract command now supports various ranges for shape numbers as well as

exporting to code. #16.
This commit is contained in:
Rob Greene 2018-06-23 23:13:17 -05:00
parent 09fd3a4bca
commit f025fef9bb
5 changed files with 230 additions and 15 deletions

View File

@ -8,6 +8,7 @@ import java.nio.file.Path;
import java.util.Objects;
import io.github.applecommander.bastools.api.shapes.exporters.ImageShapeExporter;
import io.github.applecommander.bastools.api.shapes.exporters.SourceShapeExporter;
import io.github.applecommander.bastools.api.shapes.exporters.TextShapeExporter;
public interface ShapeExporter {
@ -51,4 +52,7 @@ public interface ShapeExporter {
public static ImageShapeExporter.Builder image() {
return new ImageShapeExporter.Builder();
}
public static SourceShapeExporter.Builder source() {
return new SourceShapeExporter.Builder();
}
}

View File

@ -16,6 +16,7 @@ public enum VectorCommand {
public final int ymove;
public final char shortCommand;
public final String longCommand;
public final boolean vertical;
public final boolean horizontal;
@ -37,6 +38,8 @@ public enum VectorCommand {
char shortCommand = "urdl".charAt(this.ordinal() & 0b011);
this.shortCommand = plot ? Character.toUpperCase(shortCommand) : shortCommand;
this.longCommand = this.name().replaceAll("_", "").toLowerCase();
}
public VectorCommand opposite() {

View File

@ -0,0 +1,135 @@
package io.github.applecommander.bastools.api.shapes.exporters;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
import java.util.function.BiConsumer;
import io.github.applecommander.bastools.api.shapes.Shape;
import io.github.applecommander.bastools.api.shapes.ShapeExporter;
import io.github.applecommander.bastools.api.shapes.ShapeTable;
import io.github.applecommander.bastools.api.shapes.VectorCommand;
public class SourceShapeExporter implements ShapeExporter {
private BiConsumer<Shape,PrintWriter> formatFunction = this::exportShapeAsBitmap;
private ShapeExporter textExporter;
private boolean skipEmptyShapes;
/** Use the {@code Builder} to create a TextShapeExporter. */
private SourceShapeExporter() {
this.textExporter = ShapeExporter.text().noBorder().build();
}
@Override
public void export(Shape shape, OutputStream outputStream) throws IOException {
PrintWriter pw = new PrintWriter(outputStream);
formatFunction.accept(shape, pw);
pw.flush();
}
@Override
public void export(ShapeTable shapeTable, OutputStream outputStream) throws IOException {
PrintWriter pw = new PrintWriter(outputStream);
shapeTable.shapes.stream()
.filter(this::displayThisShape)
.forEach(shape -> formatFunction.accept(shape, pw));
pw.flush();
}
private boolean displayThisShape(Shape shape) {
return !(skipEmptyShapes && shape.isEmpty());
}
public void exportShapeAsBitmap(Shape shape, PrintWriter pw) {
try {
pw.printf(".bitmap\n");
ByteArrayOutputStream os = new ByteArrayOutputStream();
textExporter.export(shape, new PaddedOutputStream(os, " "));
pw.print(new String(os.toByteArray()));
pw.printf("\n");
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
public void exportShapeAsShortCommands(Shape shape, PrintWriter pw) {
pw.printf(".short\n");
pw.printf(" %s\n", shape.toVector().toShortCommands());
pw.printf("\n");
}
public void exportShapeAsLongCommands(Shape shape, PrintWriter pw) {
pw.printf(".long\n");
Queue<VectorCommand> vectors = new LinkedList<>(shape.toVector().vectors);
while (!vectors.isEmpty()) {
VectorCommand vector = vectors.remove();
int count = 1;
while (vectors.peek() == vector) {
vectors.remove();
count += 1;
}
if (count == 1) {
pw.printf(" %s\n", vector.longCommand);
} else {
pw.printf(" %s %d\n", vector.longCommand, count);
}
}
pw.printf("\n");
}
public static class Builder {
private SourceShapeExporter exporter = new SourceShapeExporter();
public Builder bitmap() {
exporter.formatFunction = exporter::exportShapeAsBitmap;
return this;
}
public Builder shortCommands() {
exporter.formatFunction = exporter::exportShapeAsShortCommands;
return this;
}
public Builder longCommands() {
exporter.formatFunction = exporter::exportShapeAsLongCommands;
return this;
}
public Builder skipEmptyShapes() {
return skipEmptyShapes(true);
}
public Builder skipEmptyShapes(boolean skipEmptyShapes) {
exporter.skipEmptyShapes = skipEmptyShapes;
return this;
}
public ShapeExporter build() {
return exporter;
}
}
public static class PaddedOutputStream extends OutputStream {
private OutputStream wrappedStream;
private boolean needPadding = true;
private byte[] padding;
public PaddedOutputStream(OutputStream outputStream, String padding) {
Objects.requireNonNull(outputStream);
Objects.requireNonNull(padding);
this.wrappedStream = outputStream;
this.padding = padding.getBytes();
}
@Override
public void write(int b) throws IOException {
if (needPadding) {
wrappedStream.write(padding);
}
needPadding = (b == '\n');
wrappedStream.write(b);
}
}
}

View File

@ -1,9 +1,14 @@
package io.github.applecommander.bastools.tools.st;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import io.github.applecommander.bastools.api.shapes.Shape;
import io.github.applecommander.bastools.api.shapes.ShapeExporter;
@ -34,17 +39,21 @@ public class ExtractCommand implements Callable<Void> {
@Option(names = "--border", description = "Set border style (none, simple, box)", showDefaultValue = Visibility.ALWAYS)
private String borderStyle = "simple";
@Option(names = "--format", description = "Select output format (text, png, gif, jpeg, bmp, wbmp)", showDefaultValue = Visibility.ALWAYS)
@Option(names = "--format", description = "Select output format (text, source, png, gif, jpeg, bmp, wbmp)", showDefaultValue = Visibility.ALWAYS)
private String outputFormat = "text";
@Option(names = "--coding", description = "Select source style (bitmap, long, short)", showDefaultValue = Visibility.ALWAYS)
private String codeStyle;
@Option(names = "--skip-empty", description = "Skip empty shapes")
private boolean skipEmptyShapesFlag = false;
@Option(names = { "-w", "--width" }, description = "Set width (defaults: text=80, image=1024)")
private int width = -1;
@Option(names = "--shape", description = "Extract specific shape")
private int shapeNum = 0;
@Option(names = "--shapes", description = "Extract specific shape(s); formats are '1' or '1-4' and can be combined with a comma",
converter = IntegerRangeTypeConverter.class)
private List<Integer> shapeNums = new ArrayList<>();
@Parameters(arity = "0..1", description = "File to process")
private Path inputFile;
@ -57,23 +66,34 @@ public class ExtractCommand implements Callable<Void> {
ShapeTable shapeTable = stdinFlag ? ShapeTable.read(System.in) : ShapeTable.read(inputFile);
if (shapeNum > 0) {
if (shapeNum <= shapeTable.shapes.size()) {
Shape shape = shapeTable.shapes.get(shapeNum-1);
if (stdoutFlag) {
exporter.export(shape, System.out);
} else {
exporter.export(shape, outputFile);
}
} else {
throw new IOException("Invalid shape number");
}
} else {
if (shapeNums.isEmpty()) {
if (stdoutFlag) {
exporter.export(shapeTable, System.out);
} else {
exporter.export(shapeTable, outputFile);
}
} else {
List<Integer> outOfRange = shapeNums.stream()
.filter(n -> n > shapeTable.shapes.size())
.collect(Collectors.toList());
if (!outOfRange.isEmpty()) {
throw new IOException("Invalid shape numbers: " + outOfRange);
}
OutputStream outputStream = System.out;
try {
if (outputFile != null) {
outputStream = Files.newOutputStream(outputFile);
}
for (int shapeNum : shapeNums) {
Shape shape = shapeTable.shapes.get(shapeNum - 1);
exporter.export(shape, outputStream);
}
} finally {
if (outputFile != null) {
outputStream.close();
}
}
}
return null;
@ -127,6 +147,30 @@ public class ExtractCommand implements Callable<Void> {
.skipEmptyShapes(skipEmptyShapesFlag)
.build();
break;
case "source":
switch (codeStyle) {
case "bitmap":
exporter = ShapeExporter.source()
.bitmap()
.skipEmptyShapes(skipEmptyShapesFlag)
.build();
break;
case "short":
exporter = ShapeExporter.source()
.shortCommands()
.skipEmptyShapes(skipEmptyShapesFlag)
.build();
break;
case "long":
exporter = ShapeExporter.source()
.longCommands()
.skipEmptyShapes(skipEmptyShapesFlag)
.build();
break;
default:
throw new IOException("Please select a valid code style");
}
break;
default:
throw new IOException("Please select a valid output format");
}

View File

@ -0,0 +1,29 @@
package io.github.applecommander.bastools.tools.st;
import java.util.ArrayList;
import java.util.List;
import picocli.CommandLine.ITypeConverter;
public class IntegerRangeTypeConverter implements ITypeConverter<List<Integer>> {
@Override
public List<Integer> convert(String value) throws Exception {
List<Integer> list = new ArrayList<>();
String[] parts = value.split(",");
for (String part : parts) {
String[] range = part.split("-");
if (range.length == 1) {
list.add(Integer.parseInt(range[0]));
} else if (range.length == 2) {
int i0 = Integer.parseInt(range[0]);
int i1 = Integer.parseInt(range[1]);
for (int i = i0; i <= i1; i++) {
list.add(i);
}
} else {
throw new RuntimeException("Expecting a single integer or two integers for a range");
}
}
return list;
}
}