Skip to content

Commit

Permalink
feat: add wordWrap utility
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jan 3, 2025
1 parent edb2073 commit c4c556e
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 1 deletion.
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,82 @@ Following are some of the conversion examples.
| 'newcastle upon tyne' | 'Newcastle upon Tyne' |
| 'newcastle \*upon\* tyne' | 'Newcastle \*upon\* Tyne' |

### wordWrap

Wrap words in a sentence after a given characters count. The sentence is always split after a word finishes, therefore some lines may exceed or may stay smaller than the provided length.

| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| `indent` | Characters to use for indenting text after the first line |
| `width` | Number of characters after which to split the line |
| `newLine` | Specify the new line character to use for splitting lines. Default to `\n` |
| `escape` | Specify a function to escape contents of one line at a time |

```ts
import string from '@poppinss/string'

const sentence = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`

const output = string.wordWrap(sentence, { width: 40 })

/**
Lorem Ipsum is simply dummy text of the
printing and typesetting industry. Lorem
Ipsum has been the industry's standard
dummy text ever since the 1500s, when an
unknown printer took a galley of type
and scrambled it to make a type specimen
book.
It has survived not only five centuries,
but also the leap into electronic
typesetting, remaining essentially
unchanged.
It was popularised in the 1960s with the
release of Letraset sheets containing
Lorem Ipsum passages, and more recently
with desktop publishing software like
Aldus PageMaker including versions of
Lorem Ipsum.
*/
```

You can also indent lines with a given character. In the following example, we indent lines with 2 spaces.

```ts
const output = string.wordWrap(sentence, {
width: 40,
indent: ' ',
})

/**
Lorem Ipsum is simply dummy text of the
printing and typesetting industry. Lorem
Ipsum has been the industry's standard
dummy text ever since the 1500s, when an
unknown printer took a galley of type
and scrambled it to make a type specimen
book.
It has survived not only five centuries,
but also the leap into electronic
typesetting, remaining essentially
unchanged.
It was popularised in the 1960s with the
release of Letraset sheets containing
Lorem Ipsum passages, and more recently
with desktop publishing software like
Aldus PageMaker including versions of
Lorem Ipsum.
*/
```

### random

Generate a cryptographically secure random string of a given length. The output value is URL safe base64 encoded string.
Expand Down
3 changes: 2 additions & 1 deletion bin/test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { expect } from '@japa/expect'
import { assert } from '@japa/assert'
import { snapshot } from '@japa/snapshot'
import { fileSystem } from '@japa/file-system'
import { expectTypeOf } from '@japa/expect-type'
import { configure, processCLIArgs, run } from '@japa/runner'

processCLIArgs(process.argv.splice(2))
configure({
files: ['tests/**/*.spec.ts'],
plugins: [expect(), assert(), fileSystem(), expectTypeOf()],
plugins: [expect(), assert(), snapshot(), fileSystem(), expectTypeOf()],
})

run()
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { excerpt } from './src/excerpt.js'
import { ordinal } from './src/ordinal.js'
import { truncate } from './src/truncate.js'
import { sentence } from './src/sentence.js'
import { wordWrap } from './src/word_wrap.js'
import milliseconds from './src/milliseconds.js'
import { interpolate } from './src/interpolate.js'
import { plural, pluralize, singular, isPlural, isSingular } from './src/pluralize.js'
Expand Down Expand Up @@ -59,6 +60,7 @@ const string = {
random,
sentence,
condenseWhitespace,
wordWrap,
seconds,
milliseconds,
bytes,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@japa/expect-type": "^2.0.2",
"@japa/file-system": "^2.3.1",
"@japa/runner": "^3.1.4",
"@japa/snapshot": "^2.0.7",
"@release-it/conventional-changelog": "^9.0.4",
"@swc/core": "^1.10.4",
"@types/node": "^22.10.4",
Expand Down
42 changes: 42 additions & 0 deletions src/word_wrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* @poppinss/string
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
* Wraps a string value to be on multiple lines after
* a certain characters limit has been hit.
*/
export function wordWrap(
value: string,
options: {
width: number
indent?: string
newLine?: string
escape?: (value: string) => string
}
) {
const width = options.width
const indent = options.indent ?? ''
const newLine = `${options.newLine ?? '\n'}${indent}`

let regexString = '.{1,' + width + '}'
regexString += '([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)'

const re = new RegExp(regexString, 'g')
const lines = value.match(re) || []
const result = lines
.map(function (line) {
if (line.slice(-1) === '\n') {
line = line.slice(0, line.length - 1)
}
return options.escape ? options.escape(line) : line
})
.join(newLine)

return result
}
100 changes: 100 additions & 0 deletions tests/word_wrap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* @poppinss/string
*
* (c) Poppinss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { test } from '@japa/runner'
import { wordWrap } from '../src/word_wrap.js'

const sentence = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`

test.group('Word wrap', () => {
test('wrap words after a certain width', ({ assert }) => {
const lines = wordWrap(sentence, { width: 40 }).split(/\n/)
assert.snapshot(lines).matchInline(`
[
"Lorem Ipsum is simply dummy text of the ",
"printing and typesetting industry. Lorem ",
"Ipsum has been the industry's standard ",
"dummy text ever since the 1500s, when an ",
"unknown printer took a galley of type ",
"and scrambled it to make a type specimen ",
"book.",
"",
"It has survived not only five centuries, ",
"but also the leap into electronic ",
"typesetting, remaining essentially ",
"unchanged.",
"",
"It was popularised in the 1960s with the ",
"release of Letraset sheets containing ",
"Lorem Ipsum passages, and more recently ",
"with desktop publishing software like ",
"Aldus PageMaker including versions of ",
"Lorem Ipsum.",
]
`)
})

test('add identation', ({ assert }) => {
const lines = wordWrap(sentence, { width: 40, indent: ' ' }).split(/\n/)
assert.snapshot(lines).matchInline(`
[
"Lorem Ipsum is simply dummy text of the ",
" printing and typesetting industry. Lorem ",
" Ipsum has been the industry's standard ",
" dummy text ever since the 1500s, when an ",
" unknown printer took a galley of type ",
" and scrambled it to make a type specimen ",
" book.",
"",
" It has survived not only five centuries, ",
" but also the leap into electronic ",
" typesetting, remaining essentially ",
" unchanged.",
"",
" It was popularised in the 1960s with the ",
" release of Letraset sheets containing ",
" Lorem Ipsum passages, and more recently ",
" with desktop publishing software like ",
" Aldus PageMaker including versions of ",
" Lorem Ipsum.",
]
`)
})

test('define custom new line', ({ assert }) => {
const lines = wordWrap(sentence, { width: 40, newLine: '<br />' }).split('<br />')
assert.snapshot(lines).matchInline(`
[
"Lorem Ipsum is simply dummy text of the ",
"printing and typesetting industry. Lorem ",
"Ipsum has been the industry's standard ",
"dummy text ever since the 1500s, when an ",
"unknown printer took a galley of type ",
"and scrambled it to make a type specimen ",
"book.
",
"It has survived not only five centuries, ",
"but also the leap into electronic ",
"typesetting, remaining essentially ",
"unchanged.
",
"It was popularised in the 1960s with the ",
"release of Letraset sheets containing ",
"Lorem Ipsum passages, and more recently ",
"with desktop publishing software like ",
"Aldus PageMaker including versions of ",
"Lorem Ipsum.",
]
`)
})
})

0 comments on commit c4c556e

Please sign in to comment.