From f025fef9bb3c2d08d20bf9d67b6b4328ca246f40 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sat, 23 Jun 2018 23:13:17 -0500 Subject: [PATCH] Extract command now supports various ranges for shape numbers as well as exporting to code. #16. --- .../bastools/api/shapes/ShapeExporter.java | 4 + .../bastools/api/shapes/VectorCommand.java | 3 + .../shapes/exporters/SourceShapeExporter.java | 135 ++++++++++++++++++ .../bastools/tools/st/ExtractCommand.java | 74 ++++++++-- .../tools/st/IntegerRangeTypeConverter.java | 29 ++++ 5 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 api/src/main/java/io/github/applecommander/bastools/api/shapes/exporters/SourceShapeExporter.java create mode 100644 tools/st/src/main/java/io/github/applecommander/bastools/tools/st/IntegerRangeTypeConverter.java diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeExporter.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeExporter.java index 5ce8d74..2cf89ce 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeExporter.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeExporter.java @@ -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(); + } } diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java index 6bac468..83a067e 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java @@ -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() { diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/exporters/SourceShapeExporter.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/exporters/SourceShapeExporter.java new file mode 100644 index 0000000..4a92954 --- /dev/null +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/exporters/SourceShapeExporter.java @@ -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 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 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); + } + } +} \ No newline at end of file diff --git a/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/ExtractCommand.java b/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/ExtractCommand.java index 7ae4c82..7ffe473 100644 --- a/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/ExtractCommand.java +++ b/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/ExtractCommand.java @@ -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 { @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 shapeNums = new ArrayList<>(); @Parameters(arity = "0..1", description = "File to process") private Path inputFile; @@ -57,23 +66,34 @@ public class ExtractCommand implements Callable { 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 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 { .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"); } diff --git a/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/IntegerRangeTypeConverter.java b/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/IntegerRangeTypeConverter.java new file mode 100644 index 0000000..c565101 --- /dev/null +++ b/tools/st/src/main/java/io/github/applecommander/bastools/tools/st/IntegerRangeTypeConverter.java @@ -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> { + @Override + public List convert(String value) throws Exception { + List 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; + } +}