Skip to content

Commit

Permalink
Support writing completely custom output for specified modes
Browse files Browse the repository at this point in the history
Fixes #13
  • Loading branch information
sunesimonsen committed Jun 24, 2015
1 parent bb25bcd commit ab4d026
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 1 deletion.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,47 @@ console.log(pen.removeFormatting().toString('ansi'));

![Remove text formatting](images/Hello world - removeFormatting.png)

### raw(...)

If you need something completely custom, you can specify the actual
string that will be serialized for each of the different modes. You
need to specify a fallback, for the modes that are not specified.

The custom output is generated at serialization time and can be a
string or a function returning a string for each mode you want to
override.

The fallback can be a pen, a function where this is a pen or a string.

```js
var pen = magicpen();
pen.addStyle('link', function (label, url) {
this.raw({
fallback: function () {
this.text(label).sp().text('(').blue(url).text(')');
},
html: function () {
return '<a href="' + url + '" alt="' + label + '">' + label + '</a>';
}
});
});
pen.link('magicpen', 'https://github.com/sunesimonsen/magicpen');
```

This will be the output in ansi mode:

```
magicpen (\x1B[34mhttps://github.com/sunesimonsen/magicpen\x1B[39m)
```

This will be the output in html mode:

```
<div style="font-family: monospace; white-space: nowrap">
<div><a href="https://github.com/sunesimonsen/magicpen" alt="magicpen">magicpen</a></div>
</div>
```

### isBlock()

Returns `true` if the output only contains a block.
Expand Down
8 changes: 8 additions & 0 deletions lib/AnsiSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,12 @@ AnsiSerializer.prototype.text = function () {
return content;
};

AnsiSerializer.prototype.raw = function (content) {
if ('ansi' in content) {
return content.ansi();
} else {
return this.serialize(content.fallback);
}
};

module.exports = AnsiSerializer;
8 changes: 8 additions & 0 deletions lib/ColoredConsoleSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,12 @@ ColoredConsoleSerializer.prototype.text = function () {
return result;
};

ColoredConsoleSerializer.prototype.raw = function (content) {
if ('coloredConsole' in content) {
return content.coloredConsole();
} else {
return this.serialize(content.fallback);
}
};

module.exports = ColoredConsoleSerializer;
8 changes: 8 additions & 0 deletions lib/HtmlSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,12 @@ HtmlSerializer.prototype.text = function () {
return content;
};

HtmlSerializer.prototype.raw = function (content) {
if ('html' in content) {
return content.html();
} else {
return this.serialize(content.fallback);
}
};

module.exports = HtmlSerializer;
63 changes: 62 additions & 1 deletion lib/MagicPen.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ function normalizeLine(line) {
return result;
}

MagicPen.prototype.getContentFromValue = function (value) {
var clone;
if (value.isMagicPen) {
return value;
} else if (typeof value === 'function') {
clone = this.clone();
value.call(clone, clone);
return clone;
} else if (typeof value === 'string') {
clone = this.clone();
clone.text(value);
return clone;
} else {
throw new Error('Requires the arguments to be:\n' +
'a pen or\n' +
'a callback appending content to a pen or\n' +
'a text');
}
};

MagicPen.prototype.write = function (options) {
if (this.styles[options.style]) {
this.styles[options.style].apply(this, options.args);
Expand Down Expand Up @@ -205,7 +225,7 @@ MagicPen.prototype.getContentFromArguments = function (args) {
} else {
throw new Error('Requires the arguments to be:\n' +
'a pen or\n' +
'a callback append content to a penor\n' +
'a callback appending content to a pen or\n' +
'a style and arguments for that style');
}
};
Expand All @@ -229,6 +249,47 @@ MagicPen.prototype.block = function () {
return this.write({ style: 'block', args: [blockOutput] });
};

MagicPen.prototype.getContentFromValue = function (value) {
var clone;
if (value.isMagicPen) {
return value;
} else if (typeof value === 'function') {
clone = this.clone();
value.call(clone, clone);
return clone;
} else if (typeof value === 'string') {
clone = this.clone();
clone.text(value);
return clone;
} else {
throw new Error('Requires the arguments to be:\n' +
'a pen or\n' +
'a callback appending content to a pen or\n' +
'a text');
}
};

MagicPen.prototype.raw = function (options) {
if (!options || typeof options.fallback === 'undefined') {
throw new Error('Requires the argument to be an object with atleast an fallback key');
}

options = extend({}, options);
Object.keys(options).forEach(function (mode) {
if (mode !== 'fallback' && typeof options[mode] === 'string') {
var output = options[mode];
options[mode] = function () {
return output;
};
}
});

options.fallback = this.getContentFromValue(options.fallback).output.map(function (line) {
return [].concat(line);
});
this.write({ style: 'raw', args: [options]});
};

function amend(output, pen) {
var lastLine = output[output.length - 1].slice();
var newOutput = output.slice(0, -1);
Expand Down
9 changes: 9 additions & 0 deletions lib/TextSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@ TextSerializer.prototype.block = function (content) {
return this.serialize(content);
};

TextSerializer.prototype.raw = function (content) {
if ('text' in content) {
return content.text();
} else {
return this.serialize(content.fallback);
}
};


module.exports = TextSerializer;
128 changes: 128 additions & 0 deletions test/magicpen.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,101 @@ describe('magicpen', function () {
});
});

describe('raw', function () {
it('requires an argument with an fallback key', function () {
expect(function () {
pen.raw({});
}, 'to throw', 'Requires the argument to be an object with atleast an fallback key');
});

it('is capable of providing custom output for different serializers', function () {
pen.raw({
fallback: function () {
this.text('foo');
},
ansi: 'bar',
html: function () {
return '<img src="..." style="width: 100em">';
}
});
expect(pen.toString('text'), 'to equal', 'foo');
expect(pen.toString('ansi'), 'to equal', 'bar');
expect(pen.toString('html'), 'to equal',
'<div style="font-family: monospace; white-space: nowrap">\n' +
' <div><img src="..." style="width: 100em"></div>\n' +
'</div>');
});

it('custom output for modes is computed at serialization time', function () {
var dynamicContent = null;
pen.raw({
fallback: function () {
this.text('foo');
},
ansi: function () {
return 'This is dynamic content: ' + dynamicContent;
}
});
dynamicContent = 'foo';
expect(pen.toString('ansi'), 'to equal', 'This is dynamic content: foo');
dynamicContent = 'bar';
expect(pen.toString('ansi'), 'to equal', 'This is dynamic content: bar');
});

it('custom content for modes can be specified as a string', function () {
pen.raw({
fallback: function () {
this.text('foo');
},
ansi: 'bar'
});
expect(pen.toString('ansi'), 'to equal', 'bar');
});

it('custom content for modes can be specified as a function', function () {
pen.raw({
fallback: function () {
this.text('foo');
},
ansi: function () { return 'bar'; }
});
expect(pen.toString('ansi'), 'to equal', 'bar');
});

it('the fallback content can be specified as a string', function () {
pen.raw({
fallback: 'foo'
});
expect(pen.toString('ansi'), 'to equal', 'foo');
});

it('the fallback content can be specified as a function that appends the output', function () {
pen.raw({
fallback: function () {
this.red('foo');
}
});
expect(pen.toString('ansi'), 'to equal', '\x1B[31mfoo\x1B[39m');
});

it('the fallback content can be specified as a pen', function () {
pen.raw({
fallback: pen.clone().red('foo')
});
expect(pen.toString('ansi'), 'to equal', '\x1B[31mfoo\x1B[39m');
});

it('falls back to the fallback content if there is no override for the mode that is being serialized', function () {
pen.raw({
fallback: function (output) {
output.text('fallback');
}
});
expect(pen.toString('text'), 'to equal', 'fallback');
expect(pen.toString('ansi'), 'to equal', 'fallback');
});
});

describe('in text mode', function () {
it('ignores unknown styles', function () {
pen.text('>').write({ style: 'test', args: ['text'] }).text('<');
Expand Down Expand Up @@ -1103,6 +1198,39 @@ describe('magicpen', function () {
});
});

describe('link example', function () {
beforeEach(function () {
pen.addStyle('link', function (label, url) {
this.raw({
fallback: function () {
this.text(label).sp().text('(').blue(url).text(')');
},
html: function () {
return '<a href="' + url + '" alt="' + label + '">' + label + '</a>';
}
});
});
pen.link('magicpen', 'https://github.com/sunesimonsen/magicpen');
});

it('in text mode', function () {
expect(pen.toString(), 'to equal',
'magicpen (https://github.com/sunesimonsen/magicpen)');
});

it('in ansi mode', function () {
expect(pen.toString('ansi'), 'to equal',
'magicpen (\x1B[34mhttps://github.com/sunesimonsen/magicpen\x1B[39m)');
});

it('in html mode', function () {
expect(pen.toString('html'), 'to equal',
'<div style="font-family: monospace; white-space: nowrap">\n' +
' <div><a href="https://github.com/sunesimonsen/magicpen" alt="magicpen">magicpen</a></div>\n' +
'</div>');
});
});

describe('ColoredConsoleSerializer', function () {
it('should output an array', function () {
var pen = magicpen();
Expand Down

0 comments on commit ab4d026

Please sign in to comment.