Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement, document, and test 'amendOrphan' #14

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,29 @@ Break a word between any two letters when the word is longer than the specified
wrap(str, {cut: true});
```

### options.amendOrphan

Type: `Boolean`

Default: `false`

Amends the last line in the case it has one word. Only occurs when:
- there's more than one line,
- the second to last line has more than one word
- the amended line would not pass [width](#options.width).

**Example:**

```js
// 1
// 12345678901234567
var str = 'This is an orphan';
wrap(str, {width: 10, amendOrphan: true});
// returns
// This is
// an orphan
```

## Related projects

* [common-words](https://www.npmjs.com/package/common-words): Updated list (JSON) of the 100 most common words in the English language. Useful for… [more](https://www.npmjs.com/package/common-words) | [homepage](https://github.com/jonschlinkert/common-words)
Expand Down Expand Up @@ -152,4 +175,4 @@ Released under the MIT license.

***

_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on August 28, 2015._
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on August 28, 2015._
69 changes: 66 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* @attribution
*/

// WS: word separator
var WS = /\s+/;

module.exports = function(str, options) {
options = options || {};
if (str == null) {
Expand All @@ -21,24 +24,84 @@ module.exports = function(str, options) {

var newline = options.newline || '\n' + indent;

function identity(str) {
return str;
function identity(str) {
return str;
};
var escape = typeof options.escape === 'function'
? options.escape
: identity;

var re = new RegExp('.{1,' + width + '}(\\s+|$)|\\S+?(\\s+|$)', 'g');
var re = new RegExp('.{1,' + width + '}(\\s+|$)|\\S+?(\\s+|$)', 'g')
, wasCutMidWord = false;

if (options.cut) {
re = new RegExp('.{1,' + width + '}', 'g');
// if the character right before the last line is not whitespace, then the
// line was cut mid-word.
wasCutMidWord = !str.charAt(str.length - (str.length % width) - 1).match(WS);
}

var lines = str.match(re) || [];

// Incompatible with cut because there's no easy way to determine whether
// the last line was cut mid-word
if (options.amendOrphan === true) {
lines = handleOrphan(lines, width, wasCutMidWord);
}

var res = indent + lines.map(escape).join(newline);

if (options.trim === true) {
res = res.replace(/[ \t]*$/gm, '');
}
return res;
};

function handleOrphan(lines, width, wasCutMidWord) {
var len = lines.length;
if (len <= 1) return lines;

var lastLine = lines[len - 1]
, secondToLastLine = lines[len - 2]
, orphanAdopter = getLastWord(secondToLastLine)
, amendedLastLine = orphanAdopter + (wasCutMidWord ? '' : ' ') + lastLine;

// we only want to handle orphans when:
// - the second to last line has more than one word
// - the last line has only a single word
// - the amended line would not pass width
var shouldHandleOrphan = secondToLastLine.trim()
.split(WS)
.length > 1
&& lastLine.trim()
.split(WS)
.length === 1
&& width >= amendedLastLine.length;

if (shouldHandleOrphan) {
// remove the last two elements from the array
lines.splice(-2);

// we need to remove trailing whitespace from the secondToLastLine in order
// to remove the orphanAdopter correctly
secondToLastLine = removeTrailingSpace(secondToLastLine);

// remove orphanAdopter
secondToLastLine = secondToLastLine.slice(0, secondToLastLine.length - orphanAdopter.length);

lines = lines.concat(secondToLastLine, amendedLastLine);
}

return lines;

// helper functions
function getLastWord(line) {
var words = line.split(WS);
// if the last element in the array is an empty string (resulting from the
// regex split), then we call pop again to get the word.
return words.pop() || words.pop();
}
function removeTrailingSpace(str) {
return str.replace(/^(.*?)[\s]*$/, '$1');
}
}
19 changes: 18 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ describe('wrap', function () {

it('should cut long words', function() {
assert.equal(wrap('Supercalifragilisticexpialidocious and Supercalifragilisticexpialidocious', {width:24, cut:true}), ' Supercalifragilisticexpi\n alidocious and Supercali\n fragilisticexpialidociou\n s');
})
});

it('should handle orphans', function() {
assert.equal(wrap('This is an orphan', {width:10, amendOrphan:true}), ' This is \n an orphan');

// handle more than a single whitespace character between orphan and orphan adopter to keep existing functionality
assert.equal(wrap('This is \t an orphan', {width:10, amendOrphan:true}), ' This is \t \n an orphan');

assert.equal(wrap('Nope, no orphan here', {width:11, amendOrphan:true}), ' Nope, no \n orphan here');

// don't handle if the amended line length would be greater than width
assert.equal(wrap("Don't amend orphan", {width:11, amendOrphan:true}), " Don't amend \n orphan");

// handle when cut = true
assert.equal(wrap('What a Supercalif', {width:10, amendOrphan:true, cut:true}), ' What a \n Supercalif');

// except when the amended cut line has length > width
assert.equal(wrap('What a Supercalif', {width:9, amendOrphan:true, cut:true}), ' What a Su\n percalif');
});
});