Skip to content

Commit

Permalink
* initial pretty-console commit
Browse files Browse the repository at this point in the history
  • Loading branch information
epochcoder committed Feb 14, 2014
0 parents commit da7f546
Show file tree
Hide file tree
Showing 6 changed files with 661 additions and 0 deletions.
30 changes: 30 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.github.epochcoder</groupId>
<artifactId>pretty-console</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>pretty-console</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>15.0</version>
</dependency>
</dependencies>
</project>
258 changes: 258 additions & 0 deletions src/main/java/com/github/epochcoder/prettyconsole/ConsoleBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package com.github.epochcoder.prettyconsole;

import com.github.epochcoder.prettyconsole.handlers.ConsoleBoxPasswordHandler;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
* represents a console display box in the system console (or any PrintStream).
* used to display friendly technical information
* @author Willie Scholtz
*/
public final class ConsoleBox {

/**
* the character to use in box corners
*/
private static final String BOX_CHAR = " + ";

/**
* the character to use to pad strings with
*/
private static final char PAD_CHAR = ' ';

/**
* the character used for the box's right and left sides
*/
private static final String END_CHAR = " | ";

/**
* the character used for the box's top, title and bottom sides
*/
private static final String TB_CHAR = "-";

/**
* the character used for representing black in a color
*/
private static final String BLACK_CHAR = " ";

/**
* the character used for representing white in a color
*/
private static final String WHITE_CHAR = "#";

/**
* the character used for representing aliasing of fonts.
* actually other colors, but since our image is only black and white
* it will represent a shade, which in this case is aliasing
*/
private static final String ALIAS_CHAR = " ";

/**
* the key value separator for names and values
*/
private static final String KEY_VALUE_SEP = " : ";


private final List<ConsoleBoxKeyHandler> handlers;
private final StringBuilder builder;
private boolean content;
private final int width;

/**
* creates a new instance of a ConsoleBox
* @param boxWidth the width of the box (in character count)
* @param title the initial title of the box, leave blank for none
*/
public ConsoleBox(int boxWidth, String title) {
this.width = boxWidth;
this.builder = new StringBuilder();
this.handlers = new ArrayList<ConsoleBoxKeyHandler>();

// add default password handler
this.handlers.add(new ConsoleBoxPasswordHandler());

if (!Strings.isNullOrEmpty(title)) {
this.title(title);
}
}

/*
* creates a new instance of a ConsoleBox with no title
* @param boxWidth the width of the box (in character count)
*/
public ConsoleBox(int boxWidth) {
this(boxWidth, null);
}

public ConsoleBox handler(ConsoleBoxKeyHandler handler) {
this.handlers.add(handler);
return this;
}

/**
* builds and writes this box to the specified output stream
* @param output
*/
public void build(PrintStream output) {
this.title("");
output.println(this.builder.toString());
}

private String padBoth(String string, String pad, int length) {
int right = (length - string.length()) / 2 + string.length();
String result = Strings.padEnd(string, right, pad.toCharArray()[0]);
return Strings.padStart(result, length, pad.toCharArray()[0]);
}

/**
* adds a title section to the console box
* @param title the title to use
* @return the current box
*/
public ConsoleBox title(String title) {
this.builder.append("\n" + BOX_CHAR).append(padBoth(title,
TB_CHAR, this.width)).append(BOX_CHAR);

return this;
}

/**
* adds an empty line section to the console box
* @return the current box
*/
public ConsoleBox empty() {
this.builder.append("\n" + BOX_CHAR).append(
padBoth("", " ", this.width)).append(BOX_CHAR);

return this;
}

/**
* generates and writes the specified text as an ASCII image into this box
* @param text the text to write as ASCII
* @param invert should the ASCII colors be inverted?
* @return the current box
*/
public ConsoleBox ascii(String text, boolean invert) {
final BufferedImage image = new BufferedImage(this.width,
32, BufferedImage.TYPE_INT_RGB);

final Graphics graphics = image.getGraphics();
final Graphics2D g2d = (Graphics2D) graphics;

g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

Font textFont = new Font("Dialog", Font.BOLD, 22);
FontMetrics textMetrics = g2d.getFontMetrics(textFont);
g2d.setFont(textFont);

int tX = (image.getWidth() / 2) - (textMetrics.stringWidth(text) / 2);
int tY = (image.getHeight() / 2) + (textMetrics.getHeight() / 2) - 5;

g2d.drawString(text, tX, tY);
g2d.drawRenderedImage(image, null);
g2d.dispose();

final int iHeight = image.getHeight();
final int iWidth = image.getWidth();

final String bChar = invert ? WHITE_CHAR : BLACK_CHAR;
final String wChar = invert ? BLACK_CHAR : WHITE_CHAR;

for (int y = 0; y < iHeight; y++) {
final StringBuilder sb = new StringBuilder();
for (int x = 0; x < iWidth; x++) {
final int rgbColor = image.getRGB(x, y);
sb.append(rgbColor == -16777216 ? bChar : rgbColor == -1 ? wChar : ALIAS_CHAR);
}

if (sb.toString().trim().isEmpty()) {
continue;
}

this.builder.append("\n" + END_CHAR)
.append(sb).append(END_CHAR);
}

return this;
}

/**
* adds a informational line to the console box,
* automatically splitting large values
* @param key the name of the value to display
* @param value the value of this line
* @return the current box
*/
public ConsoleBox line(String key, String value) {
key = Strings.isNullOrEmpty(key) ? "null" : key;
value = Strings.isNullOrEmpty(value) ? "" : value;

// get the key length
final int kL = key.length();
// calculate remaining box space for the value
final int ths = (this.width - kL - KEY_VALUE_SEP.length());
Preconditions.checkState(ths > -1, "key[" + key + "] is to long "
+ "for box with a " + width + " width!");

// \n | the_key_length_in_spaces
final String joinOn = ("\n" + END_CHAR + Strings.padEnd("",
kL + KEY_VALUE_SEP.length(), PAD_CHAR));

// get key handlers and modify if neccessary
for (ConsoleBoxKeyHandler handler : this.handlers) {
if (handler.shouldHandle(key)) {
value = handler.handleValue(key, value);
// don't break, possibilitty of multiple handlers
}
}

// if a key handler returns null, a key should be skipped
if (value != null) {
// split the string on either length or new lines
Iterable<String> splitted = Splitter.on(Pattern
.compile("(?<=\\G.{" + ths + "})|\\n")).split(value);

// add the value + end characters (multiple lines)
String formatted = Joiner.on(joinOn).join(
Iterables.transform(splitted, new Function<String, String>() {
@Override
public String apply(String input) {
return Strings.padEnd(input, ths, ' ') + END_CHAR;
}
}));

// write completed line to builder
this.builder.append("\n" + END_CHAR).append(key)
.append(KEY_VALUE_SEP).append(formatted);

this.content = true;
}

return this;
}

/**
* @return true if {@link #line(java.lang.String, java.lang.String)}
* has been called at least once
*/
public boolean hasContent() {
return content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.github.epochcoder.prettyconsole;

/**
* interface for defining handlers for certain console box keys
* @author Willie Scholtz
* @version 1.43
*/
public interface ConsoleBoxKeyHandler {

/**
* determines if the specified key should be handled by this key handler
* @param key the key to check
* @return true if this key should be handled by this key handler
*/
public boolean shouldHandle(final String key);

/**
* if this handler is set to handle the specified key it, will execute this method to handle it's value
* @param key the key being handled
* @param value the original value to handle, note that console box automatically handles new lines and long lines
* @return the formatted value for ConsoleBox, or null if the whole line should be skipped
*/
public String handleValue(final String key, final String value);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.github.epochcoder.prettyconsole.handlers;

import com.github.epochcoder.prettyconsole.ConsoleBoxKeyHandler;
import java.util.regex.Pattern;

/**
* ensures no passwords are present in the ConsoleBox
* @author Willie Scholtz
* @version 1.43
*/
public class ConsoleBoxPasswordHandler implements ConsoleBoxKeyHandler {
/**
* pattern defining which attributes should be blurred
*/
private static final Pattern IGNORE_ATT_PATT = Pattern
.compile("pass(word)?", Pattern.CASE_INSENSITIVE);

/**
* replacement value for sensitive data
*/
private static final String SAFE_REPLACEMENT = "*****";

/**
* determines if a certain parameter is safe to transmit over the wire
* @param key the name of the parameter that will be sent
* @return true if the parameter may be sent
*/
private static boolean safeToTransmit(final String key) {
return !IGNORE_ATT_PATT.matcher(key).find();
}

@Override
public boolean shouldHandle(String key) {
return !safeToTransmit(key);
}

@Override
public String handleValue(String key, String value) {
return SAFE_REPLACEMENT;
}
}
Loading

0 comments on commit da7f546

Please sign in to comment.