Early work on shape tools and 'st' command.

This commit is contained in:
Rob Greene 2018-06-17 19:03:48 -05:00
parent 6f3c68a0ee
commit 37683e1852
15 changed files with 969 additions and 0 deletions

View File

@ -0,0 +1,81 @@
package io.github.applecommander.bastools.api.shapes;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
public class BitmapShape implements Shape {
public final List<List<Boolean>> grid = new ArrayList<>();
public final Point origin = new Point();
public BitmapShape() {
this(0,0);
}
public BitmapShape(int height, int width) {
while (grid.size() < height) {
grid.add(newRow(width));
}
}
private List<Boolean> newRow(int width) {
List<Boolean> row = new ArrayList<>();
while (row.size() < width) {
row.add(Boolean.FALSE);
}
return row;
}
public void insertColumn() {
origin.y++;
for (List<Boolean> row : grid) {
row.add(0, Boolean.FALSE);
}
}
public void addColumn() {
for (List<Boolean> row : grid) {
row.add(Boolean.FALSE);
}
}
public void insertRow() {
origin.x++;
grid.add(0, newRow(getWidth()));
}
public void addRow() {
grid.add(newRow(getWidth()));
}
public int getHeight() {
return grid.size();
}
public int getWidth() {
return grid.isEmpty() ? 0 : grid.get(0).size();
}
public void plot(int x, int y) {
plot(x, y, Boolean.TRUE);
}
public void plot(int x, int y, Boolean pixel) {
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
return;
}
grid.get(y).set(x, pixel);
}
public Boolean get(int x, int y) {
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
return Boolean.FALSE;
}
return grid.get(y).get(x);
}
@Override
public BitmapShape toBitmap() {
return this;
}
@Override
public VectorShape toVector() {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,6 @@
package io.github.applecommander.bastools.api.shapes;
public interface Shape {
public BitmapShape toBitmap();
public VectorShape toVector();
}

View File

@ -0,0 +1,50 @@
package io.github.applecommander.bastools.api.shapes;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import io.github.applecommander.bastools.api.shapes.exporters.TextShapeExporter;
public interface ShapeExporter {
/** Export a single shape to the OutputStream. */
public void export(Shape shape, OutputStream outputStream);
/** Export a single shape to the File. */
public default void export(Shape shape, File file) throws IOException {
Objects.requireNonNull(shape);
Objects.requireNonNull(file);
export(shape, file.toPath());
}
/** Export a single shape to the Path. */
public default void export(Shape shape, Path path) throws IOException {
Objects.requireNonNull(shape);
Objects.requireNonNull(path);
try (OutputStream outputStream = Files.newOutputStream(path)) {
export(shape, outputStream);
}
}
/** Export the entire shape table to the OutputStream. */
public void export(ShapeTable shapeTable, OutputStream outputStream);
/** Export the entire shape table to the File. */
public default void export(ShapeTable shapeTable, File file) throws IOException {
Objects.requireNonNull(shapeTable);
Objects.requireNonNull(file);
export(shapeTable, file.toPath());
}
/** Export the entire shape table to the Path. */
public default void export(ShapeTable shapeTable, Path path) throws IOException {
Objects.requireNonNull(shapeTable);
Objects.requireNonNull(path);
try (OutputStream outputStream = Files.newOutputStream(path)) {
export(shapeTable, outputStream);
}
}
public static TextShapeExporter.Builder text() {
return new TextShapeExporter.Builder();
}
}

View File

@ -0,0 +1,76 @@
package io.github.applecommander.bastools.api.shapes;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.github.applecommander.bastools.api.utils.Streams;
public class ShapeTable {
public static ShapeTable read(byte[] data) {
Objects.requireNonNull(data);
ShapeTable shapeTable = new ShapeTable();
ByteBuffer buf = ByteBuffer.wrap(data)
.order(ByteOrder.LITTLE_ENDIAN);
int count = Byte.toUnsignedInt(buf.get());
// unused:
buf.get();
for (int i = 0; i < count; i++) {
int offset = buf.getShort();
// load empty shapes as empty...
if (offset == 0) {
shapeTable.shapes.add(new VectorShape());
continue;
}
// defer to VectorShape to process bits
buf.mark();
buf.position(offset);
shapeTable.shapes.add(VectorShape.from(buf));
buf.reset();
}
return shapeTable;
}
public static ShapeTable read(InputStream inputStream) throws IOException {
Objects.requireNonNull(inputStream);
return read(Streams.toByteArray(inputStream));
}
public static ShapeTable read(File file) throws IOException {
Objects.requireNonNull(file);
return read(file.toPath());
}
public static ShapeTable read(Path path) throws IOException {
Objects.requireNonNull(path);
return read(Files.readAllBytes(path));
}
public final List<Shape> shapes = new ArrayList<>();
public void write(OutputStream outputStream) throws IOException {
Objects.requireNonNull(outputStream);
// TODO
}
public void write(File file) throws IOException {
Objects.requireNonNull(file);
try (OutputStream outputStream = new FileOutputStream(file)) {
write(file);
}
}
public void write(Path path) throws IOException {
Objects.requireNonNull(path);
write(path.toFile());
}
}

View File

@ -0,0 +1,32 @@
package io.github.applecommander.bastools.api.shapes;
/**
* Represents all "plot vectors" available in an Applesoft shape table.
*
* @see <a href="https://archive.org/stream/Applesoft_BASIC_Programming_Reference_Manual_Apple_Computer#page/n103/mode/2up">
* Applesoft BASIC Programming Reference Manual</a>
*/
public enum VectorCommand {
// Order here is specific to the encoding within the shape itself
MOVE_UP, MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT,
PLOT_UP, PLOT_RIGHT, PLOT_DOWN, PLOT_LEFT;
public final boolean plot;
public final int xmove;
public final int ymove;
private VectorCommand() {
this.plot = (this.ordinal() & 0b100) != 0;
// up 0b00
// right 0b01
// down 0b10
// left 0b11
if ((this.ordinal() & 0b001) == 1) {
this.xmove = 2 - (this.ordinal() & 0b011);
this.ymove = 0;
} else {
this.xmove = 0;
this.ymove = (this.ordinal() & 0b011) - 1;
}
}
}

View File

@ -0,0 +1,86 @@
package io.github.applecommander.bastools.api.shapes;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class VectorShape implements Shape {
public static VectorShape from(ByteBuffer buf) {
Objects.requireNonNull(buf);
VectorShape shape = new VectorShape();
VectorCommand[] commands = VectorCommand.values();
while (buf.hasRemaining()) {
int code = Byte.toUnsignedInt(buf.get());
if (code == 0) break;
int vector1 = code & 0b111;
int vector2 = (code >> 3) & 0b111;
int vector3 = (code >> 6) & 0b011; // Cannot plot
shape.vectors.add(commands[vector1]);
if (vector2 != 0 || vector3 != 0) {
shape.vectors.add(commands[vector2]);
if (vector3 != 0) {
shape.vectors.add(commands[vector3]);
}
}
}
return shape;
}
public final List<VectorCommand> vectors = new ArrayList<>();
public VectorShape moveUp() { return add(VectorCommand.MOVE_UP); }
public VectorShape moveRight() { return add(VectorCommand.MOVE_RIGHT); }
public VectorShape moveDown() { return add(VectorCommand.MOVE_DOWN); }
public VectorShape moveLeft() { return add(VectorCommand.MOVE_LEFT); }
public VectorShape plotUp() { return add(VectorCommand.PLOT_UP); }
public VectorShape plotRight() { return add(VectorCommand.PLOT_RIGHT); }
public VectorShape plotDown() { return add(VectorCommand.PLOT_DOWN); }
public VectorShape plotLeft() { return add(VectorCommand.PLOT_LEFT); }
private VectorShape add(VectorCommand vectorCommand) {
this.vectors.add(vectorCommand);
return this;
}
@Override
public BitmapShape toBitmap() {
BitmapShape shape = new BitmapShape();
int x = 0;
int y = 0;
for (VectorCommand command : vectors) {
if (command.plot) {
while (y < 0) {
shape.insertRow();
y += 1;
}
while (y >= shape.getHeight()) {
shape.addRow();
}
while (x < 0) {
shape.insertColumn();
x += 1;
}
while (x >= shape.getWidth()) {
shape.addColumn();
}
shape.plot(x,y);
}
x += command.xmove;
y += command.ymove;
}
return shape;
}
@Override
public VectorShape toVector() {
return this;
}
}

View File

@ -0,0 +1,245 @@
package io.github.applecommander.bastools.api.shapes.exporters;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import io.github.applecommander.bastools.api.shapes.BitmapShape;
import io.github.applecommander.bastools.api.shapes.Shape;
import io.github.applecommander.bastools.api.shapes.ShapeExporter;
import io.github.applecommander.bastools.api.shapes.ShapeTable;
public class TextShapeExporter implements ShapeExporter {
private int maxWidth = 80;
private BorderStrategy borderStrategy = BorderStrategy.BOX_DRAWING;
/** Use the {@code Builder} to create a TextShapeExporter. */
private TextShapeExporter() { }
@Override
public void export(Shape shape, OutputStream outputStream) {
Objects.requireNonNull(shape);
Objects.requireNonNull(outputStream);
BitmapShape b = shape.toBitmap();
PrintWriter pw = new PrintWriter(outputStream);
drawTopLine(pw, 1, b.getWidth());
Queue<BitmapShape> bqueue = new LinkedList<>(Arrays.asList(b));
drawRow(pw, bqueue, 1, b.getHeight(), b.getWidth());
drawBottomLine(pw, 1, b.getWidth());
pw.flush();
}
@Override
public void export(ShapeTable shapeTable, OutputStream outputStream) {
Objects.requireNonNull(shapeTable);
Objects.requireNonNull(outputStream);
List<BitmapShape> blist = shapeTable.shapes.stream()
.map(Shape::toBitmap)
.collect(Collectors.toList());
int width = blist.stream().mapToInt(BitmapShape::getWidth).max().getAsInt();
int height = blist.stream().mapToInt(BitmapShape::getHeight).max().getAsInt();
int columns = Math.max(1, this.maxWidth / width);
PrintWriter pw = new PrintWriter(outputStream);
drawTopLine(pw, columns, width);
Queue<BitmapShape> bqueue = new LinkedList<>(blist);
drawRow(pw, bqueue, columns, height, width);
while (!bqueue.isEmpty()) {
drawDividerLine(pw, columns, width);
drawRow(pw, bqueue, columns, height, width);
}
drawBottomLine(pw, columns, width);
pw.flush();
}
private void drawTopLine(PrintWriter pw, int columns, int width) {
borderStrategy.topLeftCorner(pw);
borderStrategy.horizontalLine(pw, width);
for (int i=1; i<columns; i++) {
borderStrategy.topDivider(pw);
borderStrategy.horizontalLine(pw, width);
}
borderStrategy.topRightCorner(pw);
}
private void drawDividerLine(PrintWriter pw, int columns, int width) {
borderStrategy.dividerLeftEdge(pw);
borderStrategy.dividerHorizontalLine(pw, width);
for (int i=1; i<columns; i++) {
borderStrategy.dividerMiddle(pw);
borderStrategy.dividerHorizontalLine(pw, width);
}
borderStrategy.dividerRightEdge(pw);
}
private void drawBottomLine(PrintWriter pw, int columns, int width) {
borderStrategy.bottomLeftCorner(pw);
borderStrategy.horizontalLine(pw, width);
for (int i=1; i<columns; i++) {
borderStrategy.bottomDivider(pw);
borderStrategy.horizontalLine(pw, width);
}
borderStrategy.bottomRightCorner(pw);
}
private void drawRow(PrintWriter pw, Queue<BitmapShape> bqueue, int columns, int height, int width) {
BitmapShape[] bshapes = new BitmapShape[columns];
for (int i=0; i<bshapes.length; i++) {
bshapes[i] = bqueue.isEmpty() ? new BitmapShape() : bqueue.remove();
}
for (int y=0; y<height; y++) {
borderStrategy.verticalLine(pw);
drawRowLine(pw, bshapes[0], width, y);
for (int c=1; c<bshapes.length; c++) {
borderStrategy.dividerVerticalLine(pw);
drawRowLine(pw, bshapes[c], width, y);
}
borderStrategy.verticalLine(pw);
pw.println();
}
}
private void drawRowLine(PrintWriter pw, BitmapShape bshape, int width, int y) {
List<Boolean> row = bshape.grid.size() > y ? bshape.grid.get(y) : new ArrayList<>();
for (int x=0; x<width; x++) {
Boolean plot = row.size() > x ? row.get(x) : Boolean.FALSE;
if (bshape.origin.x == x && bshape.origin.y == y) {
pw.printf("%c", plot ? '*' : '+');
} else {
pw.printf("%c", plot ? 'X' : '.');
}
}
}
public enum BorderStrategy {
/** No border but with spaces between shapes. Note the tricky newline in {@code dividerLeftEdge}. */
NONE('\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', ' ', '\0', '\n', '\0', '\0'),
/**
* A border comprised of the box drawing characters.
* @see <a href='https://en.wikipedia.org/wiki/Box-drawing_character'>Wikipedia article on box characters</a>
*/
BOX_DRAWING('\u2500', '\u2502', '\u250C', '\u2510', '\u2514', '\u2518', '\u252C', '\u2534',
'\u2502', '\u2500', '\u251C', '\u2524', '\u253C'),
/** A simple border based on plain ASCII characters. */
ASCII_TEXT('-', '|', '+', '+', '+', '+', '+', '+', '|', '-', '+', '+', '+');
private final char horizontalLine;
private final char verticalLine;
private final char topLeftCorner;
private final char topRightCorner;
private final char bottomLeftCorner;
private final char bottomRightCorner;
private final char topDivider;
private final char bottomDivider;
private final char dividerVerticalLine;
private final char dividerHorizontalLine;
private final char dividerLeftEdge;
private final char dividerRightEdge;
private final char dividerMiddle;
private BorderStrategy(char horizontalLine, char verticalLine, char topLeftCorner, char topRightCorner,
char bottomLeftCorner, char bottomRightCorner, char topDivider, char bottomDivider,
char dividerVerticalLine, char dividerHorizontalLine, char dividerLeftEdge, char dividerRightEdge,
char dividerMiddle) {
this.horizontalLine = horizontalLine;
this.verticalLine = verticalLine;
this.topLeftCorner = topLeftCorner;
this.topRightCorner = topRightCorner;
this.bottomLeftCorner = bottomLeftCorner;
this.bottomRightCorner = bottomRightCorner;
this.topDivider = topDivider;
this.bottomDivider = bottomDivider;
this.dividerVerticalLine = dividerVerticalLine;
this.dividerHorizontalLine = dividerHorizontalLine;
this.dividerLeftEdge = dividerLeftEdge;
this.dividerRightEdge = dividerRightEdge;
this.dividerMiddle = dividerMiddle;
}
private void print(Consumer<String> output, char ch) {
print(output, ch, 1);
}
private void print(Consumer<String> output, char ch, int width) {
if (ch != '\0') {
output.accept(new String(new char[width]).replace('\0', ch));
}
}
public void horizontalLine(PrintWriter pw, int width) {
print(pw::print, horizontalLine, width);
}
public void verticalLine(PrintWriter pw) {
print(pw::print, verticalLine);
}
public void topLeftCorner(PrintWriter pw) {
print(pw::print, topLeftCorner);
}
public void topRightCorner(PrintWriter pw) {
print(pw::println, topRightCorner);
}
public void bottomLeftCorner(PrintWriter pw) {
print(pw::print, bottomLeftCorner);
}
public void bottomRightCorner(PrintWriter pw) {
print(pw::println, bottomRightCorner);
}
public void topDivider(PrintWriter pw) {
print(pw::print, topDivider);
}
public void bottomDivider(PrintWriter pw) {
print(pw::print, bottomDivider);
}
public void dividerVerticalLine(PrintWriter pw) {
print(pw::print, dividerVerticalLine);
}
public void dividerHorizontalLine(PrintWriter pw, int width) {
print(pw::print, dividerHorizontalLine, width);
}
public void dividerLeftEdge(PrintWriter pw) {
print(pw::print, dividerLeftEdge);
}
public void dividerRightEdge(PrintWriter pw) {
print(pw::println, dividerRightEdge);
}
public void dividerMiddle(PrintWriter pw) {
print(pw::print, dividerMiddle);
}
}
public static class Builder {
private TextShapeExporter textShapeExporter = new TextShapeExporter();
public Builder maxWidth(int maxWidth) {
textShapeExporter.maxWidth = maxWidth;
return this;
}
public Builder noBorder() {
return borderStrategy(BorderStrategy.NONE);
}
public Builder asciiTextBorder() {
return borderStrategy(BorderStrategy.ASCII_TEXT);
}
public Builder boxDrawingBorder() {
return borderStrategy(BorderStrategy.BOX_DRAWING);
}
public Builder borderStrategy(BorderStrategy borderStrategy) {
textShapeExporter.borderStrategy = borderStrategy;
return this;
}
public ShapeExporter build() {
return textShapeExporter;
}
}
}

View File

@ -0,0 +1,23 @@
package io.github.applecommander.bastools.api.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class Streams {
private Streams() { /* Prevent construction */ }
/** Utility method to read all bytes from an InputStream. */
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();
}
}

View File

@ -0,0 +1,143 @@
package io.github.applecommander.bastools.api.shapes;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.ByteArrayOutputStream;
import org.junit.Test;
public class ShapesTest {
/**
* This shape is taken from the Applesoft BASIC Programmer's Reference Manual (1987), p146.
*/
public ShapeTable readStandardShapeTable() {
final byte[] sample = { 0x01, 0x00, 0x04, 0x00, 0x12, 0x3F, 0x20, 0x64, 0x2d, 0x15, 0x36, 0x1e, 0x07, 0x00 };
ShapeTable st = ShapeTable.read(sample);
assertNotNull(st);
assertNotNull(st.shapes);
assertEquals(1, st.shapes.size());
return st;
}
@Test
public void testStandardShapeTableVectors() {
ShapeTable st = readStandardShapeTable();
VectorShape expected = new VectorShape()
.moveDown().moveDown()
.plotLeft().plotLeft()
.moveUp().plotUp().plotUp().plotUp()
.moveRight().plotRight().plotRight().plotRight()
.moveDown().plotDown().plotDown().plotDown()
.moveLeft().plotLeft();
Shape s = st.shapes.get(0);
assertNotNull(s);
assertEquals(expected.vectors, s.toVector().vectors);
}
@Test
public void testStandardShapeTableBitmap() {
ShapeTable st = readStandardShapeTable();
BitmapShape expected = new BitmapShape(5, 5);
for (int i=1; i<=3; i++) {
expected.plot(i, 0);
expected.plot(i, 4);
expected.plot(0, i);
expected.plot(4, i);
}
Shape s = st.shapes.get(0);
assertEquals(expected.grid, s.toBitmap().grid);
}
@Test
public void testTextShapeExporterNoBorder() {
ShapeTable st = readStandardShapeTable();
final String expected = ".XXX.\n"
+ "X...X\n"
+ "X.+.X\n"
+ "X...X\n"
+ ".XXX.\n";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ShapeExporter exp = ShapeExporter.text().noBorder().build();
exp.export(st.shapes.get(0), outputStream);
String actual = new String(outputStream.toByteArray());
assertEquals(expected, actual);
}
@Test
public void testTextShapeExporterAsciiBorder() {
ShapeTable st = readStandardShapeTable();
final String expected = "+-----+\n"
+ "|.XXX.|\n"
+ "|X...X|\n"
+ "|X.+.X|\n"
+ "|X...X|\n"
+ "|.XXX.|\n"
+ "+-----+\n";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ShapeExporter exp = ShapeExporter.text().asciiTextBorder().build();
exp.export(st.shapes.get(0), outputStream);
String actual = new String(outputStream.toByteArray());
assertEquals(expected, actual);
}
@Test
public void testTextShapeTableExporterNoBorder() {
ShapeTable st = readStandardShapeTable();
// Simulate 4 of these identical shapes by adding 3 more
st.shapes.add(st.shapes.get(0));
st.shapes.add(st.shapes.get(0));
st.shapes.add(st.shapes.get(0));
final String oneExpectedRow = ".XXX. .XXX.\n"
+ "X...X X...X\n"
+ "X.+.X X.+.X\n"
+ "X...X X...X\n"
+ ".XXX. .XXX.\n";
String expected = oneExpectedRow + "\n" + oneExpectedRow;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ShapeExporter exp = ShapeExporter.text().maxWidth(12).noBorder().build();
exp.export(st, outputStream);
String actual = new String(outputStream.toByteArray());
assertEquals(expected, actual);
}
@Test
public void testTextShapeTableExporterAsciiBorder() {
ShapeTable st = readStandardShapeTable();
// Simulate 4 of these identical shapes by adding 3 more
st.shapes.add(st.shapes.get(0));
st.shapes.add(st.shapes.get(0));
st.shapes.add(st.shapes.get(0));
final String divider = "+-----+-----+\n";
final String oneExpectedRow = divider
+ "|.XXX.|.XXX.|\n"
+ "|X...X|X...X|\n"
+ "|X.+.X|X.+.X|\n"
+ "|X...X|X...X|\n"
+ "|.XXX.|.XXX.|\n";
String expected = oneExpectedRow + oneExpectedRow + divider;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ShapeExporter exp = ShapeExporter.text().maxWidth(12).asciiTextBorder().build();
exp.export(st, outputStream);
String actual = new String(outputStream.toByteArray());
assertEquals(expected, actual);
}
}

View File

@ -0,0 +1,32 @@
package io.github.applecommander.bastools.api.shapes;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class VectorCommandTest {
@Test
public void testDirections() {
test(0, 1, VectorCommand.MOVE_DOWN, VectorCommand.PLOT_DOWN);
test(0, -1, VectorCommand.MOVE_UP, VectorCommand.PLOT_UP);
test(-1, 0, VectorCommand.MOVE_LEFT, VectorCommand.PLOT_LEFT);
test(1, 0, VectorCommand.MOVE_RIGHT, VectorCommand.PLOT_RIGHT);
}
public void test(int xmove, int ymove, VectorCommand... commands) {
for (VectorCommand command : commands) {
assertEquals(xmove, command.xmove);
assertEquals(ymove, command.ymove);
}
}
@Test
public void testPlot() {
test(false, VectorCommand.MOVE_DOWN, VectorCommand.MOVE_LEFT, VectorCommand.MOVE_RIGHT, VectorCommand.MOVE_UP);
test(true, VectorCommand.PLOT_DOWN, VectorCommand.PLOT_LEFT, VectorCommand.PLOT_RIGHT, VectorCommand.PLOT_UP);
}
public void test(boolean plot, VectorCommand... commands) {
for (VectorCommand command : commands) {
assertEquals(plot, command.plot);
}
}
}

View File

@ -1,7 +1,9 @@
include 'api'
include 'tools:bt'
include 'tools:st'
rootProject.name = 'bastools'
project(":api").name = 'bastools-api'
project(":tools").name = 'bastools-tools'
project(":tools:bt").name = 'bastools-tools-bt'
project(":tools:st").name = 'bastools-tools-st'

26
tools/st/build.gradle Normal file
View File

@ -0,0 +1,26 @@
plugins {
id 'org.springframework.boot' version '2.0.2.RELEASE'
}
repositories {
jcenter()
}
apply plugin: 'application'
mainClassName = "io.github.applecommander.bastools.tools.st.Main"
bootJar {
manifest {
attributes(
'Implementation-Title': 'Shape Tools CLI',
'Implementation-Version': "${version} (${new Date().format('yyyy-MM-dd HH:mm')})"
)
}
}
dependencies {
compile 'info.picocli:picocli:3.0.2'
compile 'net.sf.applecommander:applesingle-api:1.2.1'
compile project(':bastools-api')
}

View File

@ -0,0 +1,102 @@
package io.github.applecommander.bastools.tools.st;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.Callable;
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.exporters.TextShapeExporter.BorderStrategy;
import picocli.CommandLine.Command;
import picocli.CommandLine.Help.Visibility;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
@Command(name = "extract", description = { "Extract shapes from shape table" },
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 = "--stdin", description = "Read from stdin")
private boolean stdinFlag;
@Option(names = "--stdout", description = "Write to stdout")
private boolean stdoutFlag;
@Option(names = { "-o", "--output" }, description = "Write to filename")
private String filename;
@Option(names = "--border", description = "Set border style (none, simple, box)", showDefaultValue = Visibility.ALWAYS)
private String borderStyle = "simple";
@Option(names = { "-w", "--width" }, description = "Set text width", showDefaultValue = Visibility.ALWAYS)
private int textWidth = 80;
@Option(names = "--shape", description = "Extract specific shape")
private int shapeNum = 0;
@Parameters(arity = "0..1", description = "File to process")
private Path file;
private BorderStrategy borderStrategy;
@Override
public Void call() throws IOException {
validateArguments();
ShapeTable shapeTable = stdinFlag ? ShapeTable.read(System.in) : ShapeTable.read(file);
ShapeExporter exporter = ShapeExporter.text()
.borderStrategy(borderStrategy)
.maxWidth(textWidth)
.build();
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, Paths.get(filename));
}
} else {
throw new IOException("Invalid shape number");
}
} else {
if (stdoutFlag) {
exporter.export(shapeTable, System.out);
} else {
exporter.export(shapeTable, Paths.get(filename));
}
}
return null;
}
private void validateArguments() throws IOException {
if (stdoutFlag && filename != null) {
throw new IOException("Please choose one of stdout or output file");
}
if ((stdinFlag && file != null) || (!stdinFlag && file == null)) {
throw new IOException("Please select ONE of stdin or file");
}
switch (borderStyle) {
case "box":
this.borderStrategy = BorderStrategy.BOX_DRAWING;
break;
case "simple":
this.borderStrategy = BorderStrategy.ASCII_TEXT;
break;
case "none":
this.borderStrategy = BorderStrategy.NONE;
break;
default:
throw new IOException("Please select a valid border strategy");
}
}
}

View File

@ -0,0 +1,48 @@
package io.github.applecommander.bastools.tools.st;
import java.util.Optional;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;
import picocli.CommandLine.Option;
/**
* Primary entry point into the Shape Tools utility.
*/
@Command(name = "st", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class,
descriptionHeading = "%n",
commandListHeading = "%nCommands:%n",
optionListHeading = "%nOptions:%n",
description = "Shape Tools utility",
subcommands = {
ExtractCommand.class,
HelpCommand.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);
}
}

View File

@ -0,0 +1,17 @@
package io.github.applecommander.bastools.tools.st;
import io.github.applecommander.applesingle.AppleSingle;
import io.github.applecommander.bastools.api.BasTools;
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[] {
String.format("%s: %s", Main.class.getPackage().getImplementationTitle(),
Main.class.getPackage().getImplementationVersion()),
String.format("%s: %s", BasTools.TITLE, BasTools.VERSION),
String.format("AppleSingle API: %s", AppleSingle.VERSION)
};
}
}