├── .gitignore
├── README.md
├── bintray.gradle
├── build.gradle
├── examples
├── Example1Simple.java
├── Example1Simple.png
├── Example2Simple.java
└── Example2Simple.png
├── install.gradle
├── settings.gradle
└── src
├── main
└── java
│ └── net
│ └── andreinc
│ └── ansiscape
│ ├── AnsiClass.java
│ ├── AnsiScape.java
│ ├── AnsiScapeContext.java
│ ├── AnsiScapeContextException.java
│ ├── AnsiSequence.java
│ └── parser
│ ├── Parser.java
│ ├── ParserUtils.java
│ ├── ParsingException.java
│ └── tokens
│ ├── EscapeClassBegin.java
│ ├── EscapeClassEnd.java
│ ├── FreeText.java
│ ├── Token.java
│ └── Tokenizer.java
└── test
└── java
└── net
└── andreinc
└── ansiscape
└── ParserUtilsTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Java template
3 | # Compiled class file
4 | *.class
5 |
6 | # Log file
7 | *.log
8 |
9 | # BlueJ files
10 | *.ctxt
11 |
12 | # Mobile Tools for Java (J2ME)
13 | .mtj.tmp/
14 |
15 | # Package Files #
16 | *.jar
17 | *.war
18 | *.ear
19 | *.zip
20 | *.tar.gz
21 | *.rar
22 |
23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
24 | hs_err_pid*
25 |
26 | .gradle/
27 | .idea/*
28 | build/
29 | gradle/
30 | gradlew
31 | gradlew.bat
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AnsiScape
2 |
3 | **AnsiScape** is a simple Java library that allows the user to format the output of the applications using [ANSI Escape Codes](https://en.wikipedia.org/wiki/ANSI_escape_code).
4 |
5 | Library is available in [jcenter()](https://bintray.com/nomemory/maven/ansiscape).
6 |
7 | **gradle:**
8 |
9 | ```groovy
10 | repositories {
11 | jcenter()
12 | }
13 |
14 | dependencies {
15 | compile 'net.andreinc.ansiscape:ansiscape:0.0.2'
16 | }
17 | ```
18 |
19 | **maven:**
20 |
21 | ```xml
22 |
23 |
24 | jcenter
25 | https://jcenter.bintray.com/
26 |
27 |
28 |
29 | net.andreinc.ansiscape
30 | ansiscape
31 | 0.0.2
32 |
33 | ```
34 |
35 | ### Example (1)
36 |
37 | ```java
38 | AnsiScape asciiScape = new AnsiScape();
39 |
40 | String formatted = asciiScape.format("{b Some Bold Text {u Also Underlined}}");
41 | String formatted2 = asciiScape.format("Romanian Flag {redBg }{yellowBg }{blueBg }");
42 |
43 | System.out.println(formatted);
44 | System.out.println(formatted2);
45 | ```
46 |
47 | The output:
48 |
49 | 
50 |
51 | ### Example (2)
52 |
53 | In this example we are defining our own escape codes classes (in a way similar to CSS classes):
54 |
55 | ```java
56 | AnsiScapeContext context = new AnsiScapeContext();
57 |
58 | // Create new escape classes that can be used as tags inside the text
59 | AnsiClass title = AnsiClass.withName("title").add(AnsiSequence.BOLD);
60 | AnsiClass url = AnsiClass.withName("url").add(AnsiSequence.UNDERLINE, AnsiSequence.BLUE);
61 | AnsiClass text = AnsiClass.withName("text").add(AnsiSequence.RED);
62 |
63 | context.add(title).add(url).add(text);
64 |
65 | AnsiScape ansiScape = new AnsiScape(context);
66 |
67 | String format = ansiScape.format("{title Bold title}\n" +
68 | "-{text Some url: {url www.google.com}};\n" +
69 | "-{text Some other url: {url {redBg www.redbackground.com}}}");
70 |
71 | System.out.println(format);
72 | ```
73 | The output:
74 |
75 | 
76 |
77 | ### Supported classes
78 |
79 | By default the following escape classes can be used:
80 |
81 | | Escape Class | Description |
82 | | ------------- | -------------|
83 | | `{b }` | Bold text |
84 | | `{bold }` | Bold text |
85 | | `{dim }` | Dim text |
86 | | `{u }` | Underlined text |
87 | | `{underline }` | Underlined text |
88 | | `{blink }` | Blink text |
89 | | `{reverse }` | Reverse text |
90 | | `{blank }` | Blank |
91 | | `{overstrike }` | Overstrike text |
92 | | `{reset }` | Resets everything inside tag |
93 | | `{black }` | Black foreground |
94 | | `{blackBg }` | Black background |
95 | | `{red }` | Red foreground |
96 | | `{redBg }` | Red foreground |
97 | | `{green }` | Green foreground |
98 | | `{greenBg }` | Green background |
99 | | `{yellow }` | Yellow foreground |
100 | | `{yellowBg }` | Yellow background |
101 | | `{blue }` | Blue foreground |
102 | | `{blueBg }` | Blue background |
103 | | `{magenta }` | Magenta foreground |
104 | | `{magentaBg }` | Magenta background |
105 | | `{cyan }` | Cyan foreground |
106 | | `{cyanBg }` | Cyan background |
107 | | `{white }` | White foreground |
108 | | `{whiteBg }` | White background |
109 |
110 | ### Notes
111 |
112 | - Library woks well with Linux and Mac terminals. It wasn't tested on Windows, but as far as I know it won't work on Windows XP or older `cmd.exe` terminals without additional hacks ;
113 | - In this stage the library is **experimental** - there are no unit tests for the moment;
114 | - Some terminals (like IntelliJ Output Console) do not implement all the ascii codes.
115 |
116 |
--------------------------------------------------------------------------------
/bintray.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.jfrog.bintray'
2 |
3 | version = '0.0.2'
4 |
5 | task sourcesJar(type: Jar) {
6 | classifier = 'sources'
7 | from sourceSets.main.allSource
8 | }
9 |
10 | task javadocJar(type: Jar, dependsOn: javadoc) {
11 | classifier 'javadoc'
12 | from javadoc.destinationDir
13 | }
14 |
15 | artifacts {
16 | archives javadocJar
17 | archives sourcesJar
18 | }
19 |
20 | // Bintray
21 | Properties properties = new Properties()
22 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
23 |
24 | bintray {
25 | user = properties.getProperty("bintray.user")
26 | key = properties.getProperty("bintray.apikey")
27 |
28 | configurations = ['archives']
29 | pkg {
30 | repo = 'maven'
31 | name = 'ansiscape'
32 | desc = 'Color your output using Ansi Escape codes'
33 | websiteUrl = 'https://github.com/nomemory/ansiscape'
34 | vcsUrl = 'https://github.com/nomemory/ansiscape'
35 | licenses = ["Apache-2.0"]
36 | publish = true
37 | publicDownloadNumbers = true
38 | }
39 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript() {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'
7 | }
8 | }
9 |
10 | plugins {
11 | id "com.jfrog.bintray" version "1.7.3"
12 | }
13 |
14 | group 'net.andreinc.ansiscape'
15 | version '0.0.2'
16 |
17 | apply plugin: 'java'
18 | apply plugin: 'idea'
19 |
20 | sourceCompatibility = 1.8
21 |
22 | repositories {
23 | mavenCentral()
24 | }
25 |
26 | dependencies {
27 | compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.6'
28 | compileOnly 'org.projectlombok:lombok:1.16.18'
29 | testCompile group: 'junit', name: 'junit', version: '4.12'
30 | }
31 |
32 | apply from: 'install.gradle'
33 | apply from: 'bintray.gradle'
34 |
--------------------------------------------------------------------------------
/examples/Example1Simple.java:
--------------------------------------------------------------------------------
1 | import net.andreinc.ansiscape.AnsiScape;
2 |
3 | public class Example1Simple {
4 | public static void main(String[] args) {
5 | AnsiScape asciiScape = new AnsiScape();
6 |
7 | String formatted = asciiScape.format("{b Some Bold Text {u Also Underlined}}");
8 | String formatted2 = asciiScape.format("Romanian Flag {redBg }{yellowBg }{blueBg }");
9 |
10 | System.out.println(formatted);
11 | System.out.println(formatted2);
12 | }
13 | }
--------------------------------------------------------------------------------
/examples/Example1Simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomemory/ansiscape/dce7c75b2591fecdd012216b3ab2b1c3b7756116/examples/Example1Simple.png
--------------------------------------------------------------------------------
/examples/Example2Simple.java:
--------------------------------------------------------------------------------
1 | import net.andreinc.ansiscape.AnsiClass;
2 | import net.andreinc.ansiscape.AnsiScape;
3 | import net.andreinc.ansiscape.AnsiScapeContext;
4 | import net.andreinc.ansiscape.AnsiSequence;
5 |
6 | public class Example2Simple {
7 | public static void main(String[] args) {
8 | AnsiScapeContext context = new AnsiScapeContext();
9 |
10 | AnsiClass title = AnsiClass.withName("title").add(AnsiSequence.BOLD);
11 | AnsiClass url = AnsiClass.withName("url").add(AnsiSequence.UNDERLINE, AnsiSequence.BLUE);
12 | AnsiClass text = AnsiClass.withName("text").add(AnsiSequence.RED);
13 |
14 | context.add(title).add(url).add(text);
15 |
16 | AnsiScape ansiScape = new AnsiScape(context);
17 |
18 | String format = ansiScape.format("{title Bold title}\n" +
19 | "-{text Some url: {url www.google.com}};\n" +
20 | "-{text Some other url: {url {redBg www.redbackground.com}}}");
21 |
22 | System.out.println(format);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/Example2Simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomemory/ansiscape/dce7c75b2591fecdd012216b3ab2b1c3b7756116/examples/Example2Simple.png
--------------------------------------------------------------------------------
/install.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 |
3 | group = 'net.andreinc.ansiscape'
4 |
5 | install {
6 | repositories.mavenInstaller {
7 | pom {
8 | project {
9 | packaging 'jar'
10 | groupId 'net.andreinc.ansiscape'
11 | artifactId 'ansiscape'
12 |
13 | name 'ansiscape' // YOUR LIBRARY NAME
14 | description 'Color your output using Ansi Escape codes'
15 | url 'https://github.com/nomemory/ansiscape'
16 |
17 | licenses {
18 | license {
19 | name 'The Apache Software License, Version 2.0'
20 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
21 | }
22 | }
23 | developers {
24 | developer {
25 | id 'nomemory' //YOUR ID
26 | name 'Andrei N. Ciobanu' //YOUR NAME
27 | email 'n@n.com' //YOUR EMAIL
28 | }
29 | }
30 | scm {
31 | connection 'https://github.com/nomemory/ansiscape' // YOUR GIT REPO
32 | developerConnection 'https://github.com/nomemory/ansiscape' // YOUR GIT REPO
33 | url 'https://github.com/nomemory/ansiscape' // YOUR SITE
34 |
35 | }
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'ansiscape'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/AnsiClass.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape;
2 |
3 | import lombok.Getter;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import static java.util.stream.Collectors.joining;
9 | import static org.apache.commons.lang3.StringUtils.isAlphanumeric;
10 | import static org.apache.commons.lang3.StringUtils.isEmpty;
11 |
12 | public class AnsiClass {
13 |
14 | @Getter private final String name;
15 | private final List ansiSequences = new ArrayList<>();
16 |
17 | private AnsiClass(String name) {
18 | this.name = name;
19 | }
20 |
21 | public static final AnsiClass withName(String name) {
22 | if (isEmpty(name) || !isAlphanumeric(name)) {
23 | String errFmt =
24 | String.format("Invalid class name: '%s'. The name should be alphanumeric and non-empty.", name);
25 | throw new IllegalArgumentException(errFmt);
26 | }
27 | return new AnsiClass(name);
28 | }
29 |
30 | public AnsiClass add(AnsiSequence... sequences) {
31 | for (AnsiSequence seq : sequences) {
32 | ansiSequences.add(seq);
33 | }
34 | return this;
35 | }
36 |
37 | public AnsiClass add(String escape) {
38 | if (isEmpty(escape)) {
39 | String errFmt =
40 | String.format("Invalid escape sequence: '%s'. This should be non-empty.", escape);
41 | throw new IllegalArgumentException(errFmt);
42 | }
43 | add(new AnsiSequence(escape));
44 | return this;
45 | }
46 |
47 | public AnsiClass inherit(AnsiClass ansiClass) {
48 | this.ansiSequences.addAll(ansiClass.ansiSequences);
49 | return this;
50 | }
51 |
52 | public String getCharSequences() {
53 | return ansiSequences.stream()
54 | .map(AnsiSequence::getSequence)
55 | .collect(joining(""));
56 | }
57 |
58 | @Override
59 | public String toString() {
60 | return "EscapeClass{" +
61 | "name='" + name + '\'' +
62 | ", escapeSequences=" + ansiSequences +
63 | '}';
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/AnsiScape.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import net.andreinc.ansiscape.parser.Parser;
7 |
8 | @NoArgsConstructor
9 | @AllArgsConstructor
10 | public class AnsiScape {
11 |
12 | public static final AnsiScape ansi = new AnsiScape();
13 |
14 | @Getter private AnsiScapeContext context = new AnsiScapeContext();
15 |
16 | public String format(String source, Object... args) {
17 | Parser parser = new Parser(context, source);
18 | return String.format(parser.parse(source), args);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/AnsiScapeContext.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape;
2 |
3 | import java.util.Collections;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 | import java.util.Set;
7 |
8 | import static net.andreinc.ansiscape.AnsiScapeContextException.asciiClassAlreadyDefined;
9 | import static net.andreinc.ansiscape.AnsiSequence.*;
10 |
11 | public class AnsiScapeContext {
12 |
13 | private final Map registeredClasses = new HashMap<>();
14 |
15 | public AnsiScapeContext() {
16 | initDefaultClasses();
17 | }
18 |
19 | private void initDefaultClasses() {
20 | registeredClasses.put("b", AnsiClass.withName("b").add(BOLD));
21 | registeredClasses.put("bold", AnsiClass.withName("b").add(BOLD));
22 | registeredClasses.put("dim", AnsiClass.withName("dim").add(DIM));
23 | registeredClasses.put("u", AnsiClass.withName("u").add(UNDERLINE));
24 | registeredClasses.put("underline", AnsiClass.withName("underline").add(UNDERLINE));
25 | registeredClasses.put("blink", AnsiClass.withName("blink").add(BLINK));
26 | registeredClasses.put("reverse", AnsiClass.withName("reverse").add(REVERSE));
27 | registeredClasses.put("blank", AnsiClass.withName("blank").add(BLANK));
28 | registeredClasses.put("overstrike", AnsiClass.withName("overstrike").add(OVERSTRIKE));
29 | registeredClasses.put("reset", AnsiClass.withName("reset").add(RESET));
30 | registeredClasses.put("black", AnsiClass.withName("black").add(BLACK));
31 | registeredClasses.put("blackBg", AnsiClass.withName("blackBg").add(BLACK_BG));
32 | registeredClasses.put("red", AnsiClass.withName("red").add(RED));
33 | registeredClasses.put("redBg", AnsiClass.withName("redBg").add(RED_BG));
34 | registeredClasses.put("green", AnsiClass.withName("green").add(GREEN));
35 | registeredClasses.put("greenBg", AnsiClass.withName("greenBg").add(GREEN_BG));
36 | registeredClasses.put("yellow", AnsiClass.withName("yellow").add(YELLOW));
37 | registeredClasses.put("yellowBg", AnsiClass.withName("yellowBg").add(YELLOW_BG));
38 | registeredClasses.put("blue", AnsiClass.withName("blue").add(BLUE));
39 | registeredClasses.put("blueBg", AnsiClass.withName("blueBg").add(BLUE_BG));
40 | registeredClasses.put("magenta", AnsiClass.withName("magenta").add(MAGENTA));
41 | registeredClasses.put("magentaBg", AnsiClass.withName("blueBg").add(MAGENTA_BG));
42 | registeredClasses.put("cyan", AnsiClass.withName("cyan").add(CYAN));
43 | registeredClasses.put("cyanBg", AnsiClass.withName("cyan").add(CYAN_BG));
44 | registeredClasses.put("white", AnsiClass.withName("white").add(WHITE));
45 | registeredClasses.put("whiteBg", AnsiClass.withName("whiteBg").add(WHITE_BG));
46 | }
47 |
48 | public Set getClassNames() {
49 | return Collections.unmodifiableSet(registeredClasses.keySet());
50 | }
51 |
52 | public AnsiScapeContext add(AnsiClass ansiClass) {
53 | if (registeredClasses.containsKey(ansiClass.getName())) {
54 | throw asciiClassAlreadyDefined(ansiClass.getName());
55 | }
56 | registeredClasses.put(ansiClass.getName(), ansiClass);
57 | return this;
58 | }
59 |
60 | public AnsiClass get(String className) {
61 | return registeredClasses.get(className);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/AnsiScapeContextException.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape;
2 |
3 | public class AnsiScapeContextException extends RuntimeException {
4 |
5 | public static final AnsiScapeContextException asciiClassAlreadyDefined(String className) {
6 | return new AnsiScapeContextException(String.format("The '%s' already exists in the context. Please choose another name.", className));
7 | }
8 |
9 | public AnsiScapeContextException(String msg) {
10 | super(msg);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/AnsiSequence.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape;
2 |
3 | import lombok.Getter;
4 |
5 | import static org.apache.commons.lang3.StringUtils.isEmpty;
6 |
7 | public class AnsiSequence {
8 |
9 | private static final String ESCAPE_FORMAT = ((char)27) + "[%sm";
10 |
11 | public static final AnsiSequence RESET = new AnsiSequence("0");
12 | public static final AnsiSequence BOLD = new AnsiSequence("1");
13 | public static final AnsiSequence DIM = new AnsiSequence("2");
14 | public static final AnsiSequence UNDERLINE = new AnsiSequence("4");
15 | public static final AnsiSequence BLINK = new AnsiSequence("5");
16 | public static final AnsiSequence REVERSE = new AnsiSequence("7");
17 | public static final AnsiSequence BLANK = new AnsiSequence("8");
18 | public static final AnsiSequence OVERSTRIKE = new AnsiSequence("9");
19 |
20 | public static final AnsiSequence BLACK = new AnsiSequence("30");
21 | public static final AnsiSequence BLACK_BG = new AnsiSequence("40");
22 | public static final AnsiSequence RED = new AnsiSequence("31");
23 | public static final AnsiSequence RED_BG = new AnsiSequence("41");
24 | public static final AnsiSequence GREEN = new AnsiSequence("32");
25 | public static final AnsiSequence GREEN_BG = new AnsiSequence("42");
26 | public static final AnsiSequence YELLOW = new AnsiSequence("33");
27 | public static final AnsiSequence YELLOW_BG = new AnsiSequence("43");
28 | public static final AnsiSequence BLUE = new AnsiSequence("34");
29 | public static final AnsiSequence BLUE_BG = new AnsiSequence("44");
30 | public static final AnsiSequence MAGENTA = new AnsiSequence("35");
31 | public static final AnsiSequence MAGENTA_BG = new AnsiSequence("45");
32 | public static final AnsiSequence CYAN = new AnsiSequence("36");
33 | public static final AnsiSequence CYAN_BG = new AnsiSequence("46");
34 | public static final AnsiSequence WHITE = new AnsiSequence("37");
35 | public static final AnsiSequence WHITE_BG = new AnsiSequence("47");
36 |
37 | public AnsiSequence(String escape) {
38 | if (isEmpty(escape)) {
39 | String errFmt =
40 | String.format("Invalid escape sequence: '%s'. This should be non-empty.", escape);
41 | throw new IllegalArgumentException(errFmt);
42 | }
43 | this.sequence = String.format(ESCAPE_FORMAT, escape);
44 | }
45 |
46 | @Getter private String sequence;
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/Parser.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser;
2 |
3 | import net.andreinc.ansiscape.AnsiClass;
4 | import net.andreinc.ansiscape.AnsiScape;
5 | import net.andreinc.ansiscape.AnsiScapeContext;
6 | import net.andreinc.ansiscape.AnsiSequence;
7 | import net.andreinc.ansiscape.parser.tokens.*;
8 | import org.apache.commons.lang3.StringUtils;
9 |
10 | import java.util.LinkedList;
11 | import java.util.List;
12 | import java.util.Set;
13 |
14 | import static net.andreinc.ansiscape.parser.ParsingException.invalidBracketSerquence;
15 | import static net.andreinc.ansiscape.parser.ParsingException.unknownEscapeClass;
16 |
17 | public class Parser {
18 |
19 | private Tokenizer tokenizer;
20 |
21 | private AnsiScapeContext ansiScapeContext;
22 | private Set classNames;
23 | private final String source;
24 |
25 | public Parser(AnsiScapeContext ansiScapeContext, String source) {
26 | this.ansiScapeContext = ansiScapeContext;
27 | this.classNames = ansiScapeContext.getClassNames();
28 | this.source = source;
29 | this.tokenizer = new Tokenizer(source);
30 | }
31 |
32 | public String parse(String source) {
33 | if (StringUtils.isEmpty(source)) {
34 | throw ParsingException.invalidSource();
35 | }
36 | StringBuilder buff = new StringBuilder();
37 | List tokens = tokenizer.getTokens();
38 | LinkedList ansiClasses = new LinkedList<>();
39 | for(Token token : tokens) {
40 | if (token instanceof EscapeClassBegin) {
41 | // Verify if the escape class is registered in the context
42 | EscapeClassBegin begin = (EscapeClassBegin) token;
43 | String escapeClassName = begin.getEscapeClassName();
44 | AnsiClass ansiClass = ansiScapeContext.get(escapeClassName);
45 | if (null == ansiClass) {
46 | throw unknownEscapeClass(escapeClassName);
47 | }
48 | buff.append(ansiClass.getCharSequences());
49 | ansiClasses.add(ansiClass);
50 | }
51 | else if (token instanceof FreeText) {
52 | buff.append(((FreeText) token).getText());
53 | }
54 | else if (token instanceof EscapeClassEnd) {
55 | if (ansiClasses.isEmpty()) {
56 | throw invalidBracketSerquence(token.getStartIndex());
57 | }
58 | ansiClasses.removeLast();
59 | buff.append(AnsiSequence.RESET.getSequence());
60 | for(AnsiClass ansiClass : ansiClasses) {
61 | buff.append(ansiClass.getCharSequences());
62 | }
63 | }
64 | }
65 | return buff.toString();
66 | }
67 |
68 | public static void main(String[] args) {
69 | AnsiScape ansiScape = new AnsiScape();
70 |
71 | String fmt = ansiScape.format("{Green Test}{blue Test}{cyan Test}", "s");
72 | System.out.println(fmt);
73 | }
74 | }
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/ParserUtils.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser;
2 |
3 | public class ParserUtils {
4 |
5 | /*
6 | * Returns all the characters starting from "start" until the end or a space is encountered.
7 | */
8 | public static String getUntilSpaceOrEnd(String source, int start) {
9 | StringBuilder buff = new StringBuilder();
10 | for(int i = start; i < source.length() && source.charAt(i) != ' '; ++i) {
11 | buff.append(source.charAt(i));
12 | }
13 | return buff.toString();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/ParsingException.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser;
2 |
3 | public class ParsingException extends RuntimeException {
4 |
5 | public static final ParsingException unknownEscapeClass(String className) {
6 | return new ParsingException(String.format("Unknown escape class: '%s'.", className));
7 | }
8 |
9 | public static final ParsingException invalidBracketSerquence(int index) {
10 | return new ParsingException(String.format("Invalid bracket sequence detected at index: %d.", index));
11 | }
12 |
13 | public static final ParsingException invalidSource() {
14 | return new ParsingException("Invalid source. String cannot be NULL or empty.");
15 | }
16 |
17 | public ParsingException(String msg) {
18 | super(msg);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/tokens/EscapeClassBegin.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser.tokens;
2 |
3 | import lombok.Getter;
4 |
5 | public class EscapeClassBegin extends Token {
6 |
7 | @Getter private String escapeClassName;
8 |
9 | public EscapeClassBegin(int index, String escapeClassName) {
10 | super(index);
11 | this.escapeClassName = escapeClassName;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "ESCAPE_CLASS_BEGIN:" + escapeClassName;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/tokens/EscapeClassEnd.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser.tokens;
2 |
3 | public class EscapeClassEnd extends Token {
4 |
5 | public EscapeClassEnd(int startIndex) {
6 | super(startIndex);
7 | }
8 |
9 | @Override
10 | public String toString() {
11 | return "ESCAPE_CLASS_END";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/tokens/FreeText.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser.tokens;
2 |
3 | import lombok.Getter;
4 |
5 | public class FreeText extends Token {
6 |
7 | @Getter private String text;
8 |
9 | public FreeText(int index, String text) {
10 | super(index);
11 | this.text = text;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "FreeText{" +
17 | "text='" + text + '\'' +
18 | '}';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/tokens/Token.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser.tokens;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | /**
7 | * This class represents the token that is obtained when parsing the text that
8 | * is formatted.
9 | */
10 | @AllArgsConstructor
11 | public abstract class Token {
12 |
13 | /**
14 | * The index where the token was first detected.
15 | */
16 | @Getter private int startIndex;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/net/andreinc/ansiscape/parser/tokens/Tokenizer.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape.parser.tokens;
2 |
3 | import net.andreinc.ansiscape.parser.ParserUtils;
4 |
5 | import java.util.LinkedList;
6 | import java.util.List;
7 |
8 | /**
9 | * This class splits the source String that is going to be formatted into a List of tokens.
10 | */
11 | public class Tokenizer {
12 |
13 | private static final char CHR_ESCAPE = '`';
14 | private static final char CHR_ESCAPE_CLASS_BEGIN = '{';
15 | private static final char CHR_ESCAPE_CLASS_END = '}';
16 |
17 | public final String source;
18 |
19 | public Tokenizer(String source) {
20 | this.source = source;
21 | }
22 |
23 | public List getTokens() {
24 | List tokens = new LinkedList<>();
25 |
26 | int i = 0;
27 | char now;
28 |
29 | StringBuilder freeText = new StringBuilder();
30 |
31 | while(i < source.length()) {
32 | now = source.charAt(i);
33 | // Escape char detected - ignoring next character
34 | if (now == CHR_ESCAPE) {
35 | freeText.append(source.charAt(i+1));
36 | i++;
37 | }
38 | else if (now == CHR_ESCAPE_CLASS_BEGIN) {
39 | // This is where we exit from potential FreeText so everything we collected
40 | // so far is going to be added in the final list of tokens
41 | appendExistingFreeText(tokens, freeText, i);
42 | // We obtain the ascii escape class name
43 | String escapeClassName = ParserUtils.getUntilSpaceOrEnd(source, i+1);
44 | tokens.add(new EscapeClassBegin(i, escapeClassName));
45 | // We skip to the character where the class name ends.
46 | i+=escapeClassName.length()+1;
47 | }
48 | else if (now == CHR_ESCAPE_CLASS_END) {
49 | // This is where we exit from potential FreeText so everything we collected
50 | // so far is going to be added in the final list of tokens
51 | appendExistingFreeText(tokens, freeText, i);
52 | tokens.add(new EscapeClassEnd(i));
53 | }
54 | else {
55 | freeText.append(now);
56 | }
57 | i++;
58 | }
59 | // If there was anything in the freetext buffer we add it to the list of tokens
60 | appendExistingFreeText(tokens, freeText, i);
61 | return tokens;
62 | }
63 |
64 | public void appendExistingFreeText(List tokens, StringBuilder freeTextBuff, int idx) {
65 | if (freeTextBuff.length()!=0) {
66 | tokens.add(new FreeText(idx, freeTextBuff.toString()));
67 | freeTextBuff.setLength(0);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/net/andreinc/ansiscape/ParserUtilsTest.java:
--------------------------------------------------------------------------------
1 | package net.andreinc.ansiscape;
2 |
3 | import net.andreinc.ansiscape.parser.ParserUtils;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | public class ParserUtilsTest {
8 |
9 | @Test
10 | public void getUntilSpaceOrEndTest1() {
11 | String test = ParserUtils.getUntilSpaceOrEnd("Ana are mere", 0);
12 | Assert.assertTrue(test.equals("Ana"));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------