Skip to content

Commit

Permalink
Add speech bubble tip
Browse files Browse the repository at this point in the history
Allo @yeoman,

I finally found the time to finalize this PR which will close #11.
It’s ready for review :octocat:

Let me know what you think.
Could the code be written more beautiful? Because it introduces mad conditions and weird numbers 😁

Closes #23
  • Loading branch information
mischah authored and sindresorhus committed Jun 17, 2016
1 parent ec79c37 commit 9af3fb1
Show file tree
Hide file tree
Showing 13 changed files with 67 additions and 12 deletions.
37 changes: 34 additions & 3 deletions index.js
Expand Up @@ -10,19 +10,27 @@ var repeating = require('repeating');
var cliBoxes = require('cli-boxes');

var border = cliBoxes.round;
var topOffset = 3;
var topOffset = 4;
var leftOffset = 17;
var defaultGreeting =
'\n _-----_' +
'\n _-----_ ' +
'\n | | ' +
'\n |' + chalk.red('--(o)--') + '| ' +
'\n `---------´ ' +
'\n ' + chalk.yellow('(') + ' _' + chalk.yellow('´U`') + '_ ' + chalk.yellow(')') + ' ' +
'\n /___A___\\ ' +
'\n /___A___\\ /' +
'\n ' + chalk.yellow('| ~ |') + ' ' +
'\n __' + chalk.yellow('\'.___.\'') + '__ ' +
'\n ´ ' + chalk.red('` |') + '° ' + chalk.red('´ Y') + ' ` ';

// A total line with 45 characters consists of:
// 28 chars for the top frame of the speech bubble → `╭──────────────────────────╮`
// 17 chars for the yeoman character »column« → ` /___A___\ /`
var TOTAL_CHARACTERS_PER_LINE = 45;

// The speech bubble will overflow the Yeoman character if the message is too long.
var MAX_MESSAGE_LINES_BEFORE_OVERFLOW = 7;

module.exports = function (message, options) {
message = (message || 'Welcome to Yeoman, ladies and gentlemen!').trim();
options = options || {};
Expand Down Expand Up @@ -125,6 +133,29 @@ module.exports = function (message, options) {
}, maxLength);

if (index === 0) {
// Need to adjust the top position of the speech bubble depending on the
// amount of lines of the message.
if (array.length === 2) {
topOffset -= 1;
}

if (array.length >= 3) {
topOffset -= 2;
}

// The speech bubble will overflow the Yeoman character if the message
// is too long. So we vertically center the bubble by adding empty lines
// on top of the greeting.
if (array.length > MAX_MESSAGE_LINES_BEFORE_OVERFLOW) {
var emptyLines = Math.ceil((array.length - MAX_MESSAGE_LINES_BEFORE_OVERFLOW) / 2);

for (var i = 0; i < emptyLines; i++) {
greeting.unshift('');
}

frame.top = pad.left(frame.top, TOTAL_CHARACTERS_PER_LINE);
}

greeting[topOffset - 1] += frame.top;
}

Expand Down
2 changes: 1 addition & 1 deletion test/fixture/ansi.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0m\u001b[31m\u001b[47mH\u001b[31m\u001b[47mi\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────────────────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
"╭──────────────────────────╮\n _-----_ \u001b[0m│\u001b[0m \u001b[0m\u001b[31m\u001b[47mH\u001b[31m\u001b[47mi\u001b[0m \u001b[0m│\u001b[0m\n | | \u001b[0m╰──────────────────────────╯\n |\u001b[31m--(o)--\u001b[39m| \n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
1 change: 1 addition & 0 deletions test/fixture/correctly-formatted-two-lines.json
@@ -0,0 +1 @@
"\n _-----_ ╭──────────────────────────╮\n | | \u001b[0m│\u001b[0m \u001b[0mWelcome to Yeoman,\u001b[0m \u001b[0m│\u001b[0m\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0mladies and gentlemen!\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────────────────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/correctly-formatted.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0mHi\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────────────────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
"\n _-----_ \n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0mHi\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────────────────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/half-ansi.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0m\u001b[31m\u001b[47mH\u001b[31m\u001b[47mi\u001b[49m\u001b[39m \u001b[49m\u001b[39mt\u001b[49m\u001b[39mh\u001b[49m\u001b[39me\u001b[49m\u001b[39mr\u001b[49m\u001b[39me\u001b[49m\u001b[39m,\u001b[49m\u001b[39m \u001b[49m\u001b[39ms\u001b[49m\u001b[39mi\u001b[49m\u001b[39mr\u001b[49m\u001b[39m!\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────────────────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
"╭──────────────────────────╮\n _-----_ \u001b[0m│\u001b[0m \u001b[0m\u001b[31m\u001b[47mH\u001b[31m\u001b[47mi\u001b[49m\u001b[39m \u001b[49m\u001b[39mt\u001b[49m\u001b[39mh\u001b[49m\u001b[39me\u001b[49m\u001b[39mr\u001b[49m\u001b[39me\u001b[49m\u001b[39m,\u001b[49m\u001b[39m \u001b[49m\u001b[39ms\u001b[49m\u001b[39mi\u001b[49m\u001b[39mr\u001b[49m\u001b[39m!\u001b[0m \u001b[0m│\u001b[0m\n | | \u001b[0m╰──────────────────────────╯\n |\u001b[31m--(o)--\u001b[39m| \n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/handle-fullwidth.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0m项目可以更新了\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────────────────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
"\n _-----_ \n | | \n |\u001b[31m--(o)--\u001b[39m| \n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/handle-new-line.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0mfirst line\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m│\u001b[0m \u001b[0msecond line\u001b[0m \u001b[0m│\u001b[0m\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \u001b[0m\u001b[0m \u001b[0m\u001b[0m \u001b[0m│\u001b[0m\n /___A___\\ \u001b[0m│\u001b[0m \u001b[0mfourth line\u001b[0m \u001b[0m│\u001b[0m\n \u001b[33m| ~ |\u001b[39m \u001b[0m╰──────────────────────────╯\n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
" \u001b[0m│\u001b[0m \u001b[0m\u001b[0m \u001b[0m│\u001b[0m\n _-----_ \u001b[0m│\u001b[0m \u001b[0mfourth line\u001b[0m \u001b[0m│\u001b[0m\n | | \u001b[0m╰──────────────────────────╯\n |\u001b[31m--(o)--\u001b[39m| \n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/length-customization.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0mHi\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m╰──────────╯\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
"\n _-----_ ╭──────────╮\n | | \u001b[0m│\u001b[0m \u001b[0mHi\u001b[0m \u001b[0m│\u001b[0m\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m╰──────────╯\n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/long-words.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0miloveunicornsiloveunicor\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m│\u001b[0m \u001b[0mnsiloveunicornsiloveunic\u001b[0m \u001b[0m│\u001b[0m\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \u001b[0m│\u001b[0m \u001b[0mornsiloveunicornsiloveun\u001b[0m \u001b[0m│\u001b[0m\n /___A___\\ \u001b[0m│\u001b[0m \u001b[0micorns\u001b[0m \u001b[0m│\u001b[0m\n \u001b[33m| ~ |\u001b[39m \u001b[0m╰──────────────────────────╯\n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
" \u001b[0m╰──────────────────────────\n _-----_ \n | | \n |\u001b[31m--(o)--\u001b[39m| \n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
1 change: 1 addition & 0 deletions test/fixture/overflow.json
@@ -0,0 +1 @@
" \u001b[0m│\u001b[0m \u001b[0min owner’s\u001b[0m \u001b[0m│\u001b[0m\n \u001b[0m│\u001b[0m \u001b[0mface like\u001b[0m \u001b[0m│\u001b[0m\n \u001b[0m│\u001b[0m \u001b[0mcamera\u001b[0m \u001b[0m│\u001b[0m\n _-----_ \u001b[0m│\u001b[0m \u001b[0mlens. Cough\u001b[0m \u001b[0m│\u001b[0m\n | | \u001b[0m│\u001b[0m \u001b[0mfurball.\u001b[0m \u001b[0m│\u001b[0m\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m╰─────────────╯\n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/override-maxLength.json
@@ -1 +1 @@
"\n _-----_\n | | ╭────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0mHello,\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m│\u001b[0m \u001b[0mbuddy!\u001b[0m \u001b[0m│\u001b[0m\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \u001b[0m╰────────╯\n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
"╭────────╮\n _-----_ \u001b[0m│\u001b[0m \u001b[0mHello,\u001b[0m \u001b[0m│\u001b[0m\n | | \u001b[0m│\u001b[0m \u001b[0mbuddy!\u001b[0m \u001b[0m│\u001b[0m\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m╰────────╯\n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
2 changes: 1 addition & 1 deletion test/fixture/wrap-ansi-styles.json
@@ -1 +1 @@
"\n _-----_\n | | ╭──────────────────────────╮\n |\u001b[31m--(o)--\u001b[39m| \u001b[0m│\u001b[0m \u001b[0m\u001b[31m\u001b[47mH\u001b[31m\u001b[47mi\u001b[49m\u001b[39m \u001b[49m\u001b[39mt\u001b[49m\u001b[39mh\u001b[49m\u001b[39me\u001b[49m\u001b[39mr\u001b[49m\u001b[39me\u001b[49m\u001b[39m,\u001b[49m\u001b[39m \u001b[49m\u001b[39ms\u001b[49m\u001b[39mi\u001b[49m\u001b[39mr\u001b[49m\u001b[39m!\u001b[49m\u001b[39m \u001b[44m\u001b[37my\u001b[44m\u001b[37mo\u001b[44m\u001b[37mu\u001b[44m\u001b[37m \u001b[44m\u001b[37ma\u001b[44m\u001b[37mr\u001b[44m\u001b[37me\u001b[0m \u001b[0m│\u001b[0m\n `---------´ \u001b[0m│\u001b[0m \u001b[0m\u001b[44m\u001b[37ml\u001b[44m\u001b[37mo\u001b[44m\u001b[37mo\u001b[44m\u001b[37mk\u001b[44m\u001b[37mi\u001b[44m\u001b[37mn\u001b[44m\u001b[37mg\u001b[39m\u001b[49m \u001b[39m\u001b[49ms\u001b[39m\u001b[49mw\u001b[39m\u001b[49me\u001b[39m\u001b[49ml\u001b[39m\u001b[49ml\u001b[39m\u001b[49m \u001b[39m\u001b[49mt\u001b[39m\u001b[49mo\u001b[39m\u001b[49md\u001b[39m\u001b[49ma\u001b[39m\u001b[49my\u001b[39m\u001b[49m!\u001b[0m \u001b[0m│\u001b[0m\n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \u001b[0m╰──────────────────────────╯\n /___A___\\ \n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
" \u001b[0m│\u001b[0m \u001b[0m\u001b[31m\u001b[47mH\u001b[31m\u001b[47mi\u001b[49m\u001b[39m \u001b[49m\u001b[39mt\u001b[49m\u001b[39mh\u001b[49m\u001b[39me\u001b[49m\u001b[39mr\u001b[49m\u001b[39me\u001b[49m\u001b[39m,\u001b[49m\u001b[39m \u001b[49m\u001b[39ms\u001b[49m\u001b[39mi\u001b[49m\u001b[39mr\u001b[49m\u001b[39m!\u001b[49m\u001b[39m \u001b[44m\u001b[37my\u001b[44m\u001b[37mo\u001b[44m\u001b[37mu\u001b[44m\u001b[37m \u001b[44m\u001b[37ma\u001b[44m\u001b[37mr\u001b[44m\u001b[37me\u001b[0m \u001b[0m│\u001b[0m\n _-----_ \u001b[0m│\u001b[0m \u001b[0m\u001b[44m\u001b[37ml\u001b[44m\u001b[37mo\u001b[44m\u001b[37mo\u001b[44m\u001b[37mk\u001b[44m\u001b[37mi\u001b[44m\u001b[37mn\u001b[44m\u001b[37mg\u001b[39m\u001b[49m \u001b[39m\u001b[49ms\u001b[39m\u001b[49mw\u001b[39m\u001b[49me\u001b[39m\u001b[49ml\u001b[39m\u001b[49ml\u001b[39m\u001b[49m \u001b[39m\u001b[49mt\u001b[39m\u001b[49mo\u001b[39m\u001b[49md\u001b[39m\u001b[49ma\u001b[39m\u001b[49my\u001b[39m\u001b[49m!\u001b[0m \u001b[0m│\u001b[0m\n | | \u001b[0m╰──────────────────────────╯\n |\u001b[31m--(o)--\u001b[39m| \n `---------´ \n \u001b[33m(\u001b[39m _\u001b[33m´U`\u001b[39m_ \u001b[33m)\u001b[39m \n /___A___\\ /\n \u001b[33m| ~ |\u001b[39m \n __\u001b[33m'.___.'\u001b[39m__ \n ´ \u001b[31m` |\u001b[39m° \u001b[31m´ Y\u001b[39m ` \n"
22 changes: 22 additions & 0 deletions test/test.js
Expand Up @@ -40,6 +40,17 @@ describe('yosay', function () {
});
});

it('should return correctly formatted string in two lines', function (done) {
var testName = 'correctly-formatted-two-lines';
var expected = yosay('Welcome to Yeoman, ladies and gentlemen!');

fs.readFile(getFixturePath(testName), function (err, data) {
assert.ifError(err);
assert.equal(JSON.parse(data), expected);
done();
});
});

it('should allow customization of line length', function (done) {
var testName = 'length-customization';
var expected = yosay('Hi', {maxLength: 8});
Expand Down Expand Up @@ -128,5 +139,16 @@ describe('yosay', function () {
done();
});
});

it('should overflow when lines exceed the default greeting', function (done) {
var testName = 'overflow';
var expected = yosay('Lie on your belly and purr when you are asleep shove bum in owner’s face like camera lens. Cough furball.', {maxLength: 11});

fs.readFile(getFixturePath(testName), function (err, data) {
assert.ifError(err);
assert.equal(JSON.parse(data), expected);
done();
});
});
});
});

0 comments on commit 9af3fb1

Please sign in to comment.