Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
339 changes: 177 additions & 162 deletions lib/ansiparse.js
Original file line number Diff line number Diff line change
@@ -1,184 +1,199 @@
function ansiparse(str) {
//
// I'm terrible at writing parsers.
//
var matchingControl = null,
matchingData = null,
matchingText = '',
ansiState = [],
result = [],
state = {},
eraseChar;
;(function() {

//
// General workflow for this thing is:
// \033\[33mText
// | | |
// | | matchingText
// | matchingData
// matchingControl
//
// In further steps we hope it's all going to be fine. It usually is.
//
function ansiparse(str) {
//
// I'm terrible at writing parsers.
//
var matchingControl = null,
matchingData = null,
matchingText = '',
ansiState = [],
result = [],
state = {},
eraseChar;

//
// Erases a char from the output
//
eraseChar = function () {
var index, text;
if (matchingText.length) {
matchingText = matchingText.substr(0, matchingText.length - 1);
}
else if (result.length) {
index = result.length - 1;
text = result[index].text;
if (text.length === 1) {
//
// A result bit was fully deleted, pop it out to simplify the final output
//
result.pop();
//
// General workflow for this thing is:
// \033\[33mText
// | | |
// | | matchingText
// | matchingData
// matchingControl
//
// In further steps we hope it's all going to be fine. It usually is.
//

//
// Erases a char from the output
//
eraseChar = function () {
var index, text;
if (matchingText.length) {
matchingText = matchingText.substr(0, matchingText.length - 1);
}
else {
result[index].text = text.substr(0, text.length - 1);
else if (result.length) {
index = result.length - 1;
text = result[index].text;
if (text.length === 1) {
//
// A result bit was fully deleted, pop it out to simplify the final output
//
result.pop();
}
else {
result[index].text = text.substr(0, text.length - 1);
}
}
}
};
};

for (var i = 0; i < str.length; i++) {
if (matchingControl != null) {
if (matchingControl == '\033' && str[i] == '\[') {
//
// We've matched full control code. Lets start matching formating data.
//
for (var i = 0; i < str.length; i++) {
if (matchingControl != null) {
if (matchingControl == '\033' && str[i] == '\[') {
//
// We've matched full control code. Lets start matching formating data.
//

//
// "emit" matched text with correct state
//
if (matchingText) {
state.text = matchingText;
result.push(state);
state = {};
matchingText = "";
}
//
// "emit" matched text with correct state
//
if (matchingText) {
state.text = matchingText;
result.push(state);
state = {};
matchingText = "";
}

matchingControl = null;
matchingData = '';
}
else {
//
// We failed to match anything - most likely a bad control code. We
// go back to matching regular strings.
//
matchingText += matchingControl + str[i];
matchingControl = null;
matchingControl = null;
matchingData = '';
}
else {
//
// We failed to match anything - most likely a bad control code. We
// go back to matching regular strings.
//
matchingText += matchingControl + str[i];
matchingControl = null;
}
continue;
}
continue;
}
else if (matchingData != null) {
if (str[i] == ';') {
//
// `;` separates many formatting codes, for example: `\033[33;43m`
// means that both `33` and `43` should be applied.
//
// TODO: this can be simplified by modifying state here.
//
ansiState.push(matchingData);
matchingData = '';
else if (matchingData != null) {
if (str[i] == ';') {
//
// `;` separates many formatting codes, for example: `\033[33;43m`
// means that both `33` and `43` should be applied.
//
// TODO: this can be simplified by modifying state here.
//
ansiState.push(matchingData);
matchingData = '';
}
else if (str[i] == 'm') {
//
// `m` finished whole formatting code. We can proceed to matching
// formatted text.
//
ansiState.push(matchingData);
matchingData = null;
matchingText = '';

//
// Convert matched formatting data into user-friendly state object.
//
// TODO: DRY.
//
ansiState.forEach(function (ansiCode) {
if (ansiparse.foregroundColors[ansiCode]) {
state.foreground = ansiparse.foregroundColors[ansiCode];
}
else if (ansiparse.backgroundColors[ansiCode]) {
state.background = ansiparse.backgroundColors[ansiCode];
}
else if (ansiCode == 39) {
delete state.foreground;
}
else if (ansiCode == 49) {
delete state.background;
}
else if (ansiparse.styles[ansiCode]) {
state[ansiparse.styles[ansiCode]] = true;
}
else if (ansiCode == 22) {
state.bold = false;
}
else if (ansiCode == 23) {
state.italic = false;
}
else if (ansiCode == 24) {
state.underline = false;
}
});
ansiState = [];
}
else {
matchingData += str[i];
}
continue;
}
else if (str[i] == 'm') {
//
// `m` finished whole formatting code. We can proceed to matching
// formatted text.
//
ansiState.push(matchingData);
matchingData = null;
matchingText = '';

//
// Convert matched formatting data into user-friendly state object.
//
// TODO: DRY.
//
ansiState.forEach(function (ansiCode) {
if (ansiparse.foregroundColors[ansiCode]) {
state.foreground = ansiparse.foregroundColors[ansiCode];
}
else if (ansiparse.backgroundColors[ansiCode]) {
state.background = ansiparse.backgroundColors[ansiCode];
}
else if (ansiCode == 39) {
delete state.foreground;
}
else if (ansiCode == 49) {
delete state.background;
}
else if (ansiparse.styles[ansiCode]) {
state[ansiparse.styles[ansiCode]] = true;
}
else if (ansiCode == 22) {
state.bold = false;
}
else if (ansiCode == 23) {
state.italic = false;
}
else if (ansiCode == 24) {
state.underline = false;
}
});
ansiState = [];
if (str[i] == '\033') {
matchingControl = str[i];
}
else if (str[i] == '\u0008') {
eraseChar();
}
else {
matchingData += str[i];
matchingText += str[i];
}
continue;
}

if (str[i] == '\033') {
matchingControl = str[i];
}
else if (str[i] == '\u0008') {
eraseChar();
}
else {
matchingText += str[i];
if (matchingText) {
state.text = matchingText + (matchingControl ? matchingControl : '');
result.push(state);
}
return result;
}

if (matchingText) {
state.text = matchingText + (matchingControl ? matchingControl : '');
result.push(state);
}
return result;
}

ansiparse.foregroundColors = {
'30': 'black',
'31': 'red',
'32': 'green',
'33': 'yellow',
'34': 'blue',
'35': 'magenta',
'36': 'cyan',
'37': 'white',
'90': 'grey'
};
ansiparse.foregroundColors = {
'30': 'black',
'31': 'red',
'32': 'green',
'33': 'yellow',
'34': 'blue',
'35': 'magenta',
'36': 'cyan',
'37': 'white',
'90': 'grey'
};

ansiparse.backgroundColors = {
'40': 'black',
'41': 'red',
'42': 'green',
'43': 'yellow',
'44': 'blue',
'45': 'magenta',
'46': 'cyan',
'47': 'white'
};
ansiparse.backgroundColors = {
'40': 'black',
'41': 'red',
'42': 'green',
'43': 'yellow',
'44': 'blue',
'45': 'magenta',
'46': 'cyan',
'47': 'white'
};

ansiparse.styles = {
'1': 'bold',
'3': 'italic',
'4': 'underline'
};
ansiparse.styles = {
'1': 'bold',
'3': 'italic',
'4': 'underline'
};

module.exports = ansiparse;
// export
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = ansiparse;
}
exports.ansiparse = ansiparse;
} else if (typeof define === 'function' && define.amd) {
define(function() { return ansiparse; })
} else {
this.ansiparse = ansiparse;
}

}).call(function () {
return this || (typeof window !== 'undefined' ? window : global);
}());