mirror of
https://github.com/chenasraf/terminal_color_parser_dart.git
synced 2026-05-17 17:38:09 +00:00
feat!: add color classes
This commit is contained in:
@@ -9,7 +9,7 @@ import 'package:terminal_color_parser/terminal_color_parser.dart';
|
||||
final coloredText = ColorParser('Hello, \x1B[32mworld\x1B[0m!').parse();
|
||||
|
||||
print(coloredText);
|
||||
// ==> ColoredText("Hello, ", 0:0, , ()), ColoredText("world", 32:0, , ()), ColoredText("!", 0:0, , ())]
|
||||
// ==> ColorToken("Hello, ", 0:0, , ()), ColorToken("world", 32:0, , ()), ColorToken("!", 0:0, , ())]
|
||||
|
||||
var i = 0;
|
||||
for (final token in coloredText) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:terminal_color_parser/src/color.dart';
|
||||
import 'package:terminal_color_parser/terminal_color_parser.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
final coloredText = ColorParser('Hello, \x1B[32mworld\x1B[0m!').parse();
|
||||
|
||||
print(coloredText);
|
||||
// ==> ColoredText("Hello, ", 0:0, , ()), ColoredText("world", 32:0, , ()), ColoredText("!", 0:0, , ())]
|
||||
// ==> ColorToken("Hello, ", 0:0, , ()), ColorToken("world", 32:0, , ()), ColorToken("!", 0:0, , ())]
|
||||
|
||||
var i = 0;
|
||||
for (final token in coloredText) {
|
||||
@@ -17,14 +18,13 @@ void main(List<String> args) async {
|
||||
|
||||
// Construct your own colored text
|
||||
final tokens = [
|
||||
ColorToken(text: 'Hello, ', fgColor: 0, bgColor: 0),
|
||||
ColorToken(text: 'Hello, '),
|
||||
ColorToken(
|
||||
text: 'world',
|
||||
fgColor: 32,
|
||||
bgColor: 0,
|
||||
fgColor: Color(32),
|
||||
styles: {TermStyle.underline},
|
||||
),
|
||||
ColorToken(text: '!', fgColor: 0, bgColor: 0),
|
||||
ColorToken(text: '!'),
|
||||
];
|
||||
|
||||
i = 0;
|
||||
|
||||
56
lib/src/color.dart
Normal file
56
lib/src/color.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
class Color {
|
||||
final dynamic value;
|
||||
|
||||
const Color(this.value);
|
||||
|
||||
static const Color none = Color(0);
|
||||
|
||||
String get formatted => value.toString();
|
||||
|
||||
@override
|
||||
String toString() => 'Color($value)';
|
||||
|
||||
@override
|
||||
operator ==(Object other) {
|
||||
if (other is Color) {
|
||||
return value == other.value;
|
||||
}
|
||||
return value == other;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => value.hashCode;
|
||||
}
|
||||
|
||||
class RGBColor extends Color {
|
||||
final int red;
|
||||
final int green;
|
||||
final int blue;
|
||||
|
||||
const RGBColor(this.red, this.green, this.blue) : super('$red;$green;$blue');
|
||||
|
||||
@override
|
||||
String get formatted => '$red;$green;$blue';
|
||||
|
||||
String get formattedForeground => '38;2;$formatted';
|
||||
String get formattedBackground => '48;2;$formatted';
|
||||
|
||||
@override
|
||||
String toString() => 'RGBColor($red, $green, $blue)';
|
||||
}
|
||||
|
||||
class ANSIColor extends Color {
|
||||
final int color;
|
||||
|
||||
const ANSIColor(this.color) : super(color);
|
||||
|
||||
bool get isForeground => (color >= 30 && color <= 37) && (color >= 90 && color <= 97);
|
||||
bool get isBackground => (color >= 40 && color <= 47) && (color >= 100 && color <= 107);
|
||||
|
||||
@override
|
||||
String get formatted => color.toString();
|
||||
|
||||
@override
|
||||
String toString() => 'ANSIColor($color)';
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'color.dart';
|
||||
import 'interfaces.dart';
|
||||
import 'reader.dart';
|
||||
import 'consts.dart';
|
||||
@@ -85,16 +86,10 @@ class ColorParser implements IReader<StringTokenValue> {
|
||||
token.setStyle(color);
|
||||
} else if (_checkBetween(color, 30, 37) ||
|
||||
_checkBetween(color, 90, 97)) {
|
||||
token.fgColor = color;
|
||||
if (_checkBetween(color, 90, 97)) {
|
||||
token.brightFg = true;
|
||||
}
|
||||
token.fgColor = ANSIColor(color);
|
||||
} else if (_checkBetween(color, 40, 47) ||
|
||||
_checkBetween(color, 100, 107)) {
|
||||
token.bgColor = color;
|
||||
if (_checkBetween(color, 100, 107)) {
|
||||
token.brightBg = true;
|
||||
}
|
||||
token.bgColor = ANSIColor(color);
|
||||
} else {
|
||||
// Catch arbitrary/unhandled codes
|
||||
token.setStyle(color);
|
||||
@@ -117,13 +112,13 @@ class ColorParser implements IReader<StringTokenValue> {
|
||||
if (colors.length < 5) {
|
||||
return token;
|
||||
}
|
||||
final rgb = '${colors[2]};${colors[3]};${colors[4]}';
|
||||
final r = int.parse(colors[2]);
|
||||
final g = int.parse(colors[3]);
|
||||
final b = int.parse(colors[4]);
|
||||
if (colors[0] == '38') {
|
||||
token.rgbFg = true;
|
||||
token.rgbFgColor = rgb;
|
||||
token.fgColor = RGBColor(r, g, b);
|
||||
} else if (colors[0] == '48') {
|
||||
token.rgbBg = true;
|
||||
token.rgbBgColor = rgb;
|
||||
token.bgColor = RGBColor(r, g, b);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
@@ -134,11 +129,9 @@ class ColorParser implements IReader<StringTokenValue> {
|
||||
}
|
||||
final color = int.parse(colors[2]);
|
||||
if (colors[0] == '38') {
|
||||
token.xterm256 = true;
|
||||
token.fgColor = color;
|
||||
token.fgColor = ANSIColor(color);
|
||||
} else if (colors[0] == '48') {
|
||||
token.xterm256 = true;
|
||||
token.bgColor = color;
|
||||
token.bgColor = ANSIColor(color);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'color.dart';
|
||||
import 'consts.dart';
|
||||
|
||||
/// Represents a string value with color information.
|
||||
@@ -10,12 +11,10 @@ class ColorToken {
|
||||
String text;
|
||||
|
||||
/// The foreground color code.
|
||||
int fgColor;
|
||||
String rgbFgColor;
|
||||
Color fgColor;
|
||||
|
||||
/// The background color code.
|
||||
int bgColor;
|
||||
String rgbBgColor;
|
||||
Color bgColor;
|
||||
|
||||
/// Whether the text is bold.
|
||||
bool get bold => styles.contains(TermStyle.bold);
|
||||
@@ -48,53 +47,33 @@ class ColorToken {
|
||||
bool get reset => styles.contains(TermStyle.reset);
|
||||
|
||||
/// Whether the text has a foreground color.
|
||||
bool get hasFgColor => fgColor != 0;
|
||||
bool get hasFgColor => fgColor != Color.none;
|
||||
|
||||
/// Whether the text has a background color.
|
||||
bool get hasBgColor => bgColor != 0;
|
||||
|
||||
/// Whether the text is an xterm256 color code. Otherwise, it is an ANSI color code.
|
||||
bool xterm256;
|
||||
|
||||
/// Whether the text is using r;g;b for foreground
|
||||
bool rgbFg;
|
||||
|
||||
/// Whether the text is using r;g;b for background
|
||||
bool rgbBg;
|
||||
|
||||
/// Whether the text is using bright foreground colors
|
||||
bool brightFg;
|
||||
|
||||
/// Whether the text is using bright background colors
|
||||
bool brightBg;
|
||||
bool get hasBgColor => bgColor != Color.none;
|
||||
|
||||
/// The styles applied to the text.
|
||||
late Set<TermStyle> styles;
|
||||
|
||||
ColorToken({
|
||||
required this.text,
|
||||
required this.fgColor,
|
||||
required this.bgColor,
|
||||
this.xterm256 = false,
|
||||
this.rgbFg = false,
|
||||
this.rgbBg = false,
|
||||
this.rgbFgColor = "",
|
||||
this.rgbBgColor = "",
|
||||
this.brightFg = false,
|
||||
this.brightBg = false,
|
||||
this.fgColor = Color.none,
|
||||
this.bgColor = Color.none,
|
||||
Set<TermStyle>? styles,
|
||||
}) : styles = styles ?? {};
|
||||
|
||||
/// Create an empty token.
|
||||
factory ColorToken.empty() => ColorToken(text: '', fgColor: 0, bgColor: 0);
|
||||
factory ColorToken.empty() => ColorToken(text: '');
|
||||
|
||||
/// Create an empty token with a reset style.
|
||||
factory ColorToken.emptyReset() =>
|
||||
ColorToken(text: '', fgColor: 0, bgColor: 0, styles: {TermStyle.reset});
|
||||
factory ColorToken.emptyReset() => ColorToken(
|
||||
text: '',
|
||||
styles: {TermStyle.reset},
|
||||
);
|
||||
|
||||
/// Create a token with default color and the given text.
|
||||
factory ColorToken.fromText(String text) =>
|
||||
ColorToken(text: text, fgColor: 0, bgColor: 0);
|
||||
ColorToken(text: text, fgColor: Color.none, bgColor: Color.none);
|
||||
|
||||
/// Returns true if the text is empty.
|
||||
bool get isEmpty => text.isEmpty;
|
||||
@@ -109,47 +88,40 @@ class ColorToken {
|
||||
/// To format the text in other ways, use the properties to get the [fgColor], [bgColor],
|
||||
/// and other [styles], and construct it to the desired output format.
|
||||
String get formatted {
|
||||
var colorCodes = '';
|
||||
if (xterm256) {
|
||||
colorCodes = '38;5;$fgColor';
|
||||
if (bgColor != 0) {
|
||||
colorCodes += ';48;5;$bgColor';
|
||||
}
|
||||
} else if (rgbFg || rgbBg) {
|
||||
if (rgbFgColor != "") {
|
||||
colorCodes = '38;2;$rgbFgColor';
|
||||
}
|
||||
if (rgbBgColor != "") {
|
||||
colorCodes += ';48;2;$rgbBgColor';
|
||||
}
|
||||
} else {
|
||||
colorCodes = fgColor == 0 ? '' : '$fgColor';
|
||||
if (bgColor != 0) {
|
||||
colorCodes += ';$bgColor';
|
||||
}
|
||||
}
|
||||
// final nonResetStyles = styles.where((x) => x != TermStyle.reset).toList();
|
||||
final styleCodes =
|
||||
styles.isNotEmpty ? styles.map((s) => Consts.codeMap[s]).join(';') : '';
|
||||
final parts = <String>[];
|
||||
|
||||
final tokens = _tokenString(
|
||||
[colorCodes, styleCodes].where((s) => s.isNotEmpty).join(';'));
|
||||
// final reset = this.reset ? _tokenString(Consts.resetByte.toString()) : '';
|
||||
// foreground
|
||||
if (fgColor is RGBColor) {
|
||||
parts.add('38;2;${fgColor.formatted}');
|
||||
} else if (fgColor != Color.none) {
|
||||
parts.add(fgColor.formatted);
|
||||
}
|
||||
|
||||
// background
|
||||
if (bgColor is RGBColor) {
|
||||
parts.add('48;2;${bgColor.formatted}');
|
||||
} else if (bgColor != Color.none) {
|
||||
parts.add(bgColor.formatted);
|
||||
}
|
||||
|
||||
final styleParts =
|
||||
styles.map((s) => Consts.codeMap[s]?.toString()).whereType<String>();
|
||||
|
||||
parts.addAll(styleParts);
|
||||
|
||||
final tokens = _tokenString(parts.where((s) => s.isNotEmpty).join(';'));
|
||||
|
||||
return '$tokens$text';
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'ColoredText(${debugProperties().join(', ')})';
|
||||
String toString() => 'ColorToken(${debugProperties().join(', ')})';
|
||||
|
||||
/// Returns a list of debug properties.
|
||||
List<String> debugProperties() => [
|
||||
'text: "${_debugString(text)}"',
|
||||
'fgColor: $fgColor',
|
||||
'bgColor: $bgColor',
|
||||
'xterm256: $xterm256',
|
||||
'rgbFG: $rgbFgColor',
|
||||
'rgbBG: $rgbBgColor',
|
||||
'styles: ${styles.map((s) => s.name)}',
|
||||
];
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:terminal_color_parser/src/color.dart';
|
||||
import 'package:terminal_color_parser/terminal_color_parser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -7,6 +8,7 @@ const inputs = [
|
||||
'\x1B[0m\x1B[1m\x1B[0m\x1B[1m\x1B[31mWelcome to SimpleMUD\x1B[0m\x1B[1m\x1B[0m',
|
||||
'\x1B[0m\x1B[37m\x1B[0m\x1B[37m\x1B[1m[\x1B[0m\x1B[37m\x1B[1m\x1B[32m10\x1B[0m\x1B[37m\x1B[1m/10]\x1B[0m\x1B[37m\x1B[0m',
|
||||
'\x1B[0m"If you are ready to advance, young fellow, you may \x1B[1m\x1B[33mtrain\x1B[0m here."\x1B[0m\x1B[1m\x1B[0m',
|
||||
'\x1B[38;2;255;0;0mRed\x1B[0m',
|
||||
];
|
||||
|
||||
void main() {
|
||||
@@ -17,53 +19,57 @@ void main() {
|
||||
expect(output, [
|
||||
ColorToken(
|
||||
text: 'You are standing in a small clearing.',
|
||||
fgColor: 32,
|
||||
bgColor: 0,
|
||||
fgColor: ANSIColor(32),
|
||||
styles: {},
|
||||
// styles: {TermStyle.reset},
|
||||
),
|
||||
ColorToken.emptyReset(),
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse colors - simple colors 2', () {
|
||||
final input = inputs[4];
|
||||
final output = ColorParser(input).parse();
|
||||
expect(output, [
|
||||
ColorToken(
|
||||
text: '"If you are ready to advance, young fellow, you may ',
|
||||
fgColor: 0,
|
||||
bgColor: 0,
|
||||
styles: {TermStyle.reset},
|
||||
),
|
||||
ColorToken(
|
||||
text: 'train',
|
||||
fgColor: 33,
|
||||
bgColor: 0,
|
||||
fgColor: ANSIColor(33),
|
||||
styles: {TermStyle.bold},
|
||||
),
|
||||
ColorToken(
|
||||
text: ' here."',
|
||||
fgColor: 0,
|
||||
bgColor: 0,
|
||||
styles: {TermStyle.reset},
|
||||
),
|
||||
ColorToken(
|
||||
text: '',
|
||||
fgColor: 0,
|
||||
bgColor: 0,
|
||||
styles: {TermStyle.reset, TermStyle.bold},
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse colors - rgb', () {
|
||||
final input = inputs[5];
|
||||
final output = ColorParser(input).parse();
|
||||
expect(output, [
|
||||
ColorToken(
|
||||
text: 'Red',
|
||||
styles: {},
|
||||
fgColor: RGBColor(255, 0, 0),
|
||||
),
|
||||
ColorToken.emptyReset(),
|
||||
]);
|
||||
});
|
||||
|
||||
test('parse colors - no colors', () {
|
||||
final input = inputs[1];
|
||||
final output = ColorParser(input).parse();
|
||||
expect(output, [
|
||||
ColorToken(
|
||||
text: 'You are standing in a small clearing.',
|
||||
fgColor: 0,
|
||||
bgColor: 0,
|
||||
styles: {},
|
||||
),
|
||||
]);
|
||||
@@ -77,3 +83,4 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user