diff --git a/package.json b/package.json index 8ec3d07..8c7f838 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,21 @@ "name": "linebreak", "version": "1.1.0", "description": "An implementation of the Unicode Line Breaking Algorithm (UAX #14)", - "source": "src/linebreaker.js", + "source": "src/main.ts", "type": "module", "main": "dist/main.cjs", "module": "dist/module.mjs", "exports": { - "import": "./dist/module.mjs", - "require": "./dist/main.cjs" + "import": { + "types": "./dist/types.d.ts", + "default": "./dist/module.mjs" + }, + "require": { + "types": "./dist/types.d.ts", + "default": "./dist/main.cjs" + } }, + "types": "./dist/types.d.ts", "files": [ "dist" ], @@ -32,12 +39,15 @@ "unicode-trie": "^2.0.0" }, "devDependencies": { + "@parcel/packager-ts": "^2.13.3", + "@parcel/transformer-typescript-types": "^2.13.3", "mocha": "^10.0.0", "parcel": "^2.13.3", - "request": "^2.88.0" + "request": "^2.88.0", + "typescript": "^5.9.2" }, "scripts": { - "test": "parcel build && mocha test/index.js --reporter landing", + "test": "parcel build && mocha test/index.js test/iter.js --reporter landing", "build": "parcel build", "prepublishOnly": "parcel build" }, diff --git a/readme.md b/readme.md index c1142d0..79ce1c5 100644 --- a/readme.md +++ b/readme.md @@ -16,27 +16,49 @@ it could be used for other things as well. You can install via npm - npm install linebreak +```sh +npm install linebreak +``` + +## Examples -## Example +### Using Iterable Protocol and `for...of` ```javascript -var LineBreaker = require('linebreak'); +import LineBreaker from 'linebreak'; + +const text = 'Hello world...\n1.5, 2.\n(Hello, world?)\nfoo(bar), foo_bar, foo-bar.'; + +for (const bk of new LineBreaker(text)) { + console.log(bk.substring.replace(/ $/, '~') + (bk.required ? '' : '')); +} -var lorem = 'lorem ipsum...'; -var breaker = new LineBreaker(lorem); -var last = 0; -var bk; +// Hello~ +// world... +// +// 1.5,~ +// 2. +// +// (Hello,~ +// world?) +// +// foo(bar),~ +// foo_bar,~ +// foo- +// bar. +``` + +### Using `nextBreak()` + +```javascript +const breaker = new LineBreaker(text); +let last = 0; +let bk = null; while (bk = breaker.nextBreak()) { // get the string between the last break and this one - var word = lorem.slice(last, bk.position); - console.log(word); - - // you can also check bk.required to see if this was a required break... - if (bk.required) { - console.log('\n\n'); - } + const substring = text.slice(last, bk.position); + console.log(substring.replace(/ $/, '~') + (bk.required ? '' : '')); last = bk.position; } diff --git a/src/linebreaker.js b/src/linebreaker.js index 3cd14c4..e70ff59 100644 --- a/src/linebreaker.js +++ b/src/linebreaker.js @@ -193,6 +193,16 @@ class LineBreaker { return null; } + + *[Symbol.iterator]() { + let breakInfo = null; + let last = 0; + while ((breakInfo = this.nextBreak()) != null) { + const substring = this.string.slice(last, breakInfo.position); + last = breakInfo.position; + yield Object.assign({ substring }, breakInfo); + }; + } } -module.exports = LineBreaker; +export default LineBreaker; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5c99c60 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,19 @@ +// @ts-ignore next line +import _LineBreaker from "./linebreaker.js"; + +interface Break { + position: number; + required: boolean; +} + +interface BreakInfo extends Break { + substring: string; +} + +interface LineBreaker { + new(input: string): LineBreaker; + nextBreak(): Break | null; + [Symbol.iterator](): Generator; +} + +export default _LineBreaker as LineBreaker; diff --git a/test/iter.js b/test/iter.js new file mode 100644 index 0000000..63ccfb9 --- /dev/null +++ b/test/iter.js @@ -0,0 +1,94 @@ +import LineBreaker from 'linebreak'; +import assert from 'assert'; + +describe('[Symbol.iterator]', function () { + const text = 'Hello world...\n1.5, 2.\n(Hello, world?)\nfoo(bar), foo_bar, foo-bar.'; + + it('works with `for...of` loop', () => { + const log = []; + const console = { log: (msg) => log.push(msg) }; + const breaker = new LineBreaker(text); + + for (const bk of breaker) { + console.log( + bk.substring.replace(/ $/, '~') + (bk.required ? '' : ''), + ); + } + + assert.deepStrictEqual(log, [ + 'Hello~', + 'world...\n', + '1.5,~', + '2.\n', + '(Hello,~', + 'world?)\n', + 'foo(bar),~', + 'foo_bar,~', + 'foo-', + 'bar.', + ]); + + // already consumed, so no more results + assert.deepStrictEqual([...breaker], []); + }); + + it('iterates to array', () => { + const expected = [ + { + position: 6, + required: false, + substring: 'Hello ' + }, + { + position: 15, + required: true, + substring: 'world...\n' + }, + { + position: 20, + required: false, + substring: '1.5, ' + }, + { + position: 23, + required: true, + substring: '2.\n' + }, + { + position: 31, + required: false, + substring: '(Hello, ' + }, + { + position: 39, + required: true, + substring: 'world?)\n' + }, + { + position: 49, + required: false, + substring: 'foo(bar), ' + }, + { + position: 58, + required: false, + substring: 'foo_bar, ' + }, + { + position: 62, + required: false, + substring: 'foo-' + }, + { + position: 66, + required: false, + substring: 'bar.' + } + ]; + + const breaker = new LineBreaker(text); + assert.deepStrictEqual([...breaker], expected); + // already consumed, so no more results + assert.deepStrictEqual([...breaker], []); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4ebeb64 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "strict": true, + "lib": ["ES2020"] + } +}