From 09e0bfb851b477ccf7b9f915718a00b3e97d86ae Mon Sep 17 00:00:00 2001 From: Stephannie Jimenez Date: Mon, 30 Oct 2023 14:53:06 -0500 Subject: [PATCH] feat: add truncate-middle string package --- .../@stdlib/string/truncate-middle/README.md | 17 +- .../truncate-middle/benchmark/benchmark.js | 156 +++++++---- .../@stdlib/string/truncate-middle/bin/cli | 13 +- .../string/truncate-middle/docs/repl.txt | 21 +- .../truncate-middle/docs/types/index.d.ts | 69 +++++ .../string/truncate-middle/docs/types/test.ts | 87 +++++- .../string/truncate-middle/docs/usage.txt | 1 + .../string/truncate-middle/etc/cli_opts.json | 3 +- .../string/truncate-middle/lib/main.js | 97 ++++--- .../string/truncate-middle/test/test.cli.js | 106 ++++++++ .../string/truncate-middle/test/test.js | 248 +++++++++++++++++- 11 files changed, 699 insertions(+), 119 deletions(-) diff --git a/lib/node_modules/@stdlib/string/truncate-middle/README.md b/lib/node_modules/@stdlib/string/truncate-middle/README.md index c41420b914c..4aef543b6cb 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/README.md +++ b/lib/node_modules/@stdlib/string/truncate-middle/README.md @@ -40,7 +40,7 @@ limitations under the License. var truncateMiddle = require( '@stdlib/string/truncate-middle' ); ``` -#### truncate( str, len\[, seq] ) +#### truncate( str, len\[, seq]\[, options] ) Truncates a string in the middle to a specified length. @@ -49,6 +49,16 @@ var out = truncateMiddle( 'beep boop', 7 ); // returns 'be...op' ``` +The function supports the following options: + +- **mode**: type of characters to return. Must be one of the following: + + - `'grapheme'`: grapheme clusters. Appropriate for strings containing visual characters which can span multiple Unicode code points (e.g., emoji). + - `'code_point'`: Unicode code points. Appropriate for strings containing visual characters which are comprised of more than one Unicode code unit (e.g., ideographic symbols and punctuation and mathematical alphanumerics). + - `'code_unit'`: UTF-16 code units. Appropriate for strings containing visual characters drawn from the basic multilingual plane (BMP) (e.g., common characters, such as those from the Latin, Greek, and Cyrillic alphabets). + + Default: `'grapheme'`. + By default, the truncated string uses the replacement sequence `'...'`. To customize the replacement sequence, provide a `seq` argument: ```javascript @@ -67,6 +77,10 @@ out = truncateMiddle( 'beep boop', 7, '!!!' );
+## Notes + +- By default, the function assumes the general case in which an input string may contain an arbitrary number of grapheme clusters. This assumption comes with a performance cost. Accordingly, if an input string is known to only contain visual characters of a particular type (e.g., only alphanumeric), one can achieve better performance by specifying the appropriate `mode` option. +
@@ -131,6 +145,7 @@ Options: --len length String length. --seq str Custom replacement sequence. Default: '...'. --split sep Delimiter for stdin data. Default: '/\\r?\\n/'. + --mode mode Type of character to return. Default: 'grapheme'. ``` diff --git a/lib/node_modules/@stdlib/string/truncate-middle/benchmark/benchmark.js b/lib/node_modules/@stdlib/string/truncate-middle/benchmark/benchmark.js index 65960685294..f187f5753a1 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/benchmark/benchmark.js +++ b/lib/node_modules/@stdlib/string/truncate-middle/benchmark/benchmark.js @@ -22,70 +22,124 @@ var bench = require( '@stdlib/bench' ); var isString = require( '@stdlib/assert/is-string' ).isPrimitive; -var fromCodePoint = require( '@stdlib/string/from-code-point' ); var pkg = require( './../package.json' ).name; var truncateMiddle = require( './../lib' ); -// FUNCTIONS // +// MAIN // -/** -* Creates a benchmark function. -* -* @private -* @param {PositiveInteger} len - string length -* @returns {Function} benchmark function -*/ -function createBenchmark( len ) { - return benchmark; - - /** - * Benchmark function. - * - * @private - * @param {Benchmark} b - benchmark instance - */ - function benchmark( b ) { - var out; - var i; - - b.tic(); - for ( i = 0; i < b.iterations; i++ ) { - out = truncateMiddle( fromCodePoint( i%126 )+'eep boop', len ); - if ( typeof out !== 'string' ) { - b.fail( 'should return a string' ); - } - } - b.toc(); - if ( !isString( out ) ) { +bench( pkg, function benchmark( b ) { + var values; + var out; + var i; + + values = [ + 'beep boop', + 'foo bar', + 'xyz abc' + ]; + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + out = truncateMiddle( values[ i%values.length ], 6 ); + if ( typeof out !== 'string' ) { b.fail( 'should return a string' ); } - b.pass( 'benchmark finished' ); - b.end(); } -} + b.toc(); + if ( !isString( out ) ) { + b.fail( 'should return a string' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); +bench( pkg+':mode=grapheme', function benchmark( b ) { + var values; + var opts; + var out; + var i; -// MAIN // + values = [ + 'beep boop', + 'foo bar', + 'xyz abc' + ]; + opts = { + 'mode': 'grapheme' + }; -/** -* Main execution sequence. -* -* @private -*/ -function main() { - var min; - var max; - var f; + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + out = truncateMiddle( values[ i%values.length ], 6, opts ); + if ( typeof out !== 'string' ) { + b.fail( 'should return a string' ); + } + } + b.toc(); + if ( !isString( out ) ) { + b.fail( 'should return a string' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); + +bench( pkg+':mode=code_point', function benchmark( b ) { + var values; + var opts; + var out; var i; - min = 1; - max = 10; + values = [ + 'beep boop', + 'foo bar', + 'xyz abc' + ]; + opts = { + 'mode': 'code_point' + }; - for ( i = min; i <= max; i++ ) { - f = createBenchmark( i ); - bench( pkg+':len='+i, f ); + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + out = truncateMiddle( values[ i%values.length ], 6, opts ); + if ( typeof out !== 'string' ) { + b.fail( 'should return a string' ); + } + } + b.toc(); + if ( !isString( out ) ) { + b.fail( 'should return a string' ); } -} + b.pass( 'benchmark finished' ); + b.end(); +}); -main(); +bench( pkg+':mode=code_unit', function benchmark( b ) { + var values; + var opts; + var out; + var i; + + values = [ + 'beep boop', + 'foo bar', + 'xyz abc' + ]; + opts = { + 'mode': 'code_unit' + }; + + b.tic(); + for ( i = 0; i < b.iterations; i++ ) { + out = truncateMiddle( values[ i%values.length ], 6, opts ); + if ( typeof out !== 'string' ) { + b.fail( 'should return a string' ); + } + } + b.toc(); + if ( !isString( out ) ) { + b.fail( 'should return a string' ); + } + b.pass( 'benchmark finished' ); + b.end(); +}); diff --git a/lib/node_modules/@stdlib/string/truncate-middle/bin/cli b/lib/node_modules/@stdlib/string/truncate-middle/bin/cli index 6dd44410a80..8229e528a15 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/bin/cli +++ b/lib/node_modules/@stdlib/string/truncate-middle/bin/cli @@ -45,6 +45,7 @@ function main() { var split; var flags; var args; + var opts; var cli; var len; @@ -62,6 +63,10 @@ function main() { if ( flags.help || flags.version ) { return; } + opts = {}; + if ( flags.mode ) { + opts.mode = flags.mode; + } // Get any provided command-line arguments: args = cli.args(); @@ -81,9 +86,9 @@ function main() { return stdin( onRead ); } if ( flags.seq ) { - console.log( truncateMiddle( args[ 0 ], len, flags.seq ) ); // eslint-disable-line no-console + console.log( truncateMiddle( args[ 0 ], len, flags.seq, opts ) ); // eslint-disable-line no-console } else { - console.log( truncateMiddle( args[ 0 ], len ) ); // eslint-disable-line no-console + console.log( truncateMiddle( args[ 0 ], len, opts ) ); // eslint-disable-line no-console } /** @@ -108,11 +113,11 @@ function main() { } if ( flags.seq ) { for ( i = 0; i < lines.length; i++ ) { - console.log( truncateMiddle( lines[ i ], len, flags.seq ) ); // eslint-disable-line no-console + console.log( truncateMiddle( lines[ i ], len, flags.seq, opts ) ); // eslint-disable-line no-console } } else { for ( i = 0; i < lines.length; i++ ) { - console.log( truncateMiddle( lines[ i ], len ) ); // eslint-disable-line no-console + console.log( truncateMiddle( lines[ i ], len, opts ) ); // eslint-disable-line no-console } } } diff --git a/lib/node_modules/@stdlib/string/truncate-middle/docs/repl.txt b/lib/node_modules/@stdlib/string/truncate-middle/docs/repl.txt index d8ce1900bfc..67d1cab4c5f 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/docs/repl.txt +++ b/lib/node_modules/@stdlib/string/truncate-middle/docs/repl.txt @@ -1,5 +1,5 @@ -{{alias}}( str, len[, seq] ) +{{alias}}( str, len[, seq][, options] ) Truncates a string in the middle to a specified length. Parameters @@ -13,6 +13,25 @@ seq: string (optional) Custom replacement sequence. Default: '...'. + options: Object (optional) + Options. + + options.mode: string (optional) + Type of characters to return. The following modes are supported: + + - grapheme: grapheme clusters. Appropriate for strings containing visual + characters which can span multiple Unicode code points (e.g., emoji). + - code_point: Unicode code points. Appropriate for strings containing + visual characters which are comprised of more than one Unicode code + unit (e.g., ideographic symbols and punctuation and mathematical + alphanumerics). + - code_unit': UTF-16 code units. Appropriate for strings containing + visual characters drawn from the basic multilingual plane (BMP) (e.g., + common characters, such as those from the Latin, Greek, and Cyrillic + alphabets). + + Default: 'grapheme'. + Returns ------- out: string diff --git a/lib/node_modules/@stdlib/string/truncate-middle/docs/types/index.d.ts b/lib/node_modules/@stdlib/string/truncate-middle/docs/types/index.d.ts index a2f969c6a6a..5977d674ed9 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/docs/types/index.d.ts +++ b/lib/node_modules/@stdlib/string/truncate-middle/docs/types/index.d.ts @@ -20,6 +20,75 @@ /// +// tslint:disable:unified-signatures + +/** +* Interface describing function options. +*/ +interface Options { + /** + * Specifies the type of characters to return (default: 'grapheme'). + * + * ## Notes + * + * - The following option values are supported: + * + * - `'grapheme'`: grapheme clusters. Appropriate for strings containing visual characters which can span multiple Unicode code points (e.g., emoji). + * - `'code_point'`: Unicode code points. Appropriate for strings containing visual characters which are comprised of more than one Unicode code unit (e.g., ideographic symbols and punctuation and mathematical alphanumerics). + * - `'code_unit'`: UTF-16 code units. Appropriate for strings containing visual characters drawn from the basic multilingual plane (BMP) (e.g., common characters, such as those from the Latin, Greek, and Cyrillic alphabets). + */ + mode?: 'grapheme' | 'code_point' | 'code_unit'; +} + +/** +* Truncates a string in the middle to a specified length. +* +* @param str - input string +* @param len - output string length (including sequence) +* @param seq - custom replacement sequence (default: `...`) +* @param options - options +* @returns truncated string +* +* @example +* var str = 'beep boop'; +* var out = truncateMiddle( str, 5, '>>>', { +* 'mode': 'code_unit' +* }); +* // returns 'b>>>p' +* +* @example +* var str = '🐺 Wolf Brothers 🐺'; +* var out = truncateMiddle( str, 6, '..', { +* 'mode': 'grapheme' +* }); +* // returns '🐺 .. 🐺' +*/ +declare function truncateMiddle( str: string, len: number, seq: string, options?:Options ): string; // tslint-disable-line max-line-length + +/** +* Truncates a string in the middle to a specified length. +* +* @param str - input string +* @param len - output string length (including sequence) +* @param options - options +* @returns truncated string +* +* @example +* var str = 'beep boop'; +* var out = truncateMiddle( str, 5, { +* 'mode': 'code_unit' +* }); +* // returns 'b...p' +* +* @example +* var str = '🐺 Wolf Brothers 🐺'; +* var out = truncateMiddle( str, 7, { +* 'mode': 'grapheme' +* }); +* // returns '🐺 ... 🐺' +*/ +declare function truncateMiddle( str: string, len: number, options?:Options ): string; // tslint-disable-line max-line-length + /** * Truncates a string in the middle to a specified length. * diff --git a/lib/node_modules/@stdlib/string/truncate-middle/docs/types/test.ts b/lib/node_modules/@stdlib/string/truncate-middle/docs/types/test.ts index 08cf129e8f7..278f51bc4ba 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/docs/types/test.ts +++ b/lib/node_modules/@stdlib/string/truncate-middle/docs/types/test.ts @@ -25,6 +25,8 @@ import truncateMiddle = require( './index' ); { truncateMiddle( 'abcdefghi', 3 ); // $ExpectType string truncateMiddle( 'abcdefghi', 10, '|' ); // $ExpectType string + truncateMiddle( 'abcdefghi', 10, {} ); // $ExpectType string + truncateMiddle( 'abcdefghi', 10, '|', {} ); // $ExpectType string } // The compiler throws an error if the function is not provided a string as its first argument... @@ -35,6 +37,27 @@ import truncateMiddle = require( './index' ); truncateMiddle( [], 6 ); // $ExpectError truncateMiddle( {}, 6 ); // $ExpectError truncateMiddle( ( x: number ): number => x, 6 ); // $ExpectError + + truncateMiddle( true, 6, '$' ); // $ExpectError + truncateMiddle( false, 6, '$' ); // $ExpectError + truncateMiddle( 3, 6, '$' ); // $ExpectError + truncateMiddle( [], 6, '$' ); // $ExpectError + truncateMiddle( {}, 6, '$' ); // $ExpectError + truncateMiddle( ( x: number ): number => x, 6, '$' ); // $ExpectError + + truncateMiddle( true, 6, {} ); // $ExpectError + truncateMiddle( false, 6, {} ); // $ExpectError + truncateMiddle( 3, 6, {} ); // $ExpectError + truncateMiddle( [], 6, {} ); // $ExpectError + truncateMiddle( {}, 6, {} ); // $ExpectError + truncateMiddle( ( x: number ): number => x, 6, {} ); // $ExpectError + + truncateMiddle( true, 6, '$', {} ); // $ExpectError + truncateMiddle( false, 6, '$', {} ); // $ExpectError + truncateMiddle( 3, 6, '$', {} ); // $ExpectError + truncateMiddle( [], 6, '$', {} ); // $ExpectError + truncateMiddle( {}, 6, '$', {} ); // $ExpectError + truncateMiddle( ( x: number ): number => x, 6, '$', {} ); // $ExpectError } // The compiler throws an error if the function is not provided a number as its second argument... @@ -42,23 +65,71 @@ import truncateMiddle = require( './index' ); truncateMiddle( 'abd', true ); // $ExpectError truncateMiddle( 'abd', false ); // $ExpectError truncateMiddle( 'abd', 'abc' ); // $ExpectError - truncateMiddle( 'abd', [], 0 ); // $ExpectError - truncateMiddle( 'abd', {}, 0 ); // $ExpectError - truncateMiddle( 'abd', ( x: number ): number => x, 0 ); // $ExpectError + truncateMiddle( 'abd', [] ); // $ExpectError + truncateMiddle( 'abd', {} ); // $ExpectError + truncateMiddle( 'abd', ( x: number ): number => x ); // $ExpectError + + truncateMiddle( 'abd', true, {} ); // $ExpectError + truncateMiddle( 'abd', false, {} ); // $ExpectError + truncateMiddle( 'abd', 'abc', {} ); // $ExpectError + truncateMiddle( 'abd', [], {} ); // $ExpectError + truncateMiddle( 'abd', {}, {} ); // $ExpectError + truncateMiddle( 'abd', ( x: number ): number => x, {} ); // $ExpectError + + truncateMiddle( 'abd', true, '$' ); // $ExpectError + truncateMiddle( 'abd', false, '$' ); // $ExpectError + truncateMiddle( 'abd', 'abc', '$' ); // $ExpectError + truncateMiddle( 'abd', [], '$' ); // $ExpectError + truncateMiddle( 'abd', {}, '$' ); // $ExpectError + truncateMiddle( 'abd', ( x: number ): number => x, '$' ); // $ExpectError + + truncateMiddle( 'abd', true, '$', {} ); // $ExpectError + truncateMiddle( 'abd', false, '$', {} ); // $ExpectError + truncateMiddle( 'abd', 'abc', '$', {} ); // $ExpectError + truncateMiddle( 'abd', [], '$', {} ); // $ExpectError + truncateMiddle( 'abd', {}, '$', {} ); // $ExpectError + truncateMiddle( 'abd', ( x: number ): number => x, '$', {} ); // $ExpectError } -// The compiler throws an error if the function is not provided a string as its third argument... +// The compiler throws an error if the function is not provided a valid third argument... { truncateMiddle( 'beep boop', 4, true ); // $ExpectError truncateMiddle( 'beep boop', 4, false ); // $ExpectError truncateMiddle( 'beep boop', 4, 123 ); // $ExpectError - truncateMiddle( 'beep boop', 4, [], 0 ); // $ExpectError - truncateMiddle( 'beep boop', 4, {}, 0 ); // $ExpectError - truncateMiddle( 'beep boop', 4, ( x: number ): number => x, 0 ); // $ExpectError + truncateMiddle( 'beep boop', 4, [] ); // $ExpectError + truncateMiddle( 'beep boop', 4, ( x: number ): number => x ); // $ExpectError +} + +// The compiler throws an error if the function is not provided a valid fourth argument... +{ + truncateMiddle( 'beep boop', 4, '%', true ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', false ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', 123 ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', [] ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', ( x: number ): number => x ); // $ExpectError +} + +// The compiler throws an error if the function is provided an invalid `mode` option... +{ + truncateMiddle( 'beep boop', 4, '%', { 'mode': true } ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', { 'mode': false } ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', { 'mode': null } ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', { 'mode': '' } ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', { 'mode': [] } ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', { 'mode': {} } ); // $ExpectError + truncateMiddle( 'beep boop', 4, '%', { 'mode': ( x: number ): number => x } ); // $ExpectError + + truncateMiddle( 'beep boop', 4, { 'mode': true } ); // $ExpectError + truncateMiddle( 'beep boop', 4, { 'mode': false } ); // $ExpectError + truncateMiddle( 'beep boop', 4, { 'mode': null } ); // $ExpectError + truncateMiddle( 'beep boop', 4, { 'mode': '' } ); // $ExpectError + truncateMiddle( 'beep boop', 4, { 'mode': [] } ); // $ExpectError + truncateMiddle( 'beep boop', 4, { 'mode': {} } ); // $ExpectError + truncateMiddle( 'beep boop', 4, { 'mode': ( x: number ): number => x } ); // $ExpectError } // The compiler throws an error if the function is provided an unsupported number of arguments... { truncateMiddle(); // $ExpectError - truncateMiddle( 'abc', 4, '|', true ); // $ExpectError + truncateMiddle( 'abcdefghi', 10, '|', {}, 5 ); // $ExpectError } diff --git a/lib/node_modules/@stdlib/string/truncate-middle/docs/usage.txt b/lib/node_modules/@stdlib/string/truncate-middle/docs/usage.txt index cefc9d416af..724f5a080fe 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/docs/usage.txt +++ b/lib/node_modules/@stdlib/string/truncate-middle/docs/usage.txt @@ -8,3 +8,4 @@ Options: --len length String length. --seq str Custom replacement sequence. Default: '...'. --split sep Delimiter for stdin data. Default: '/\\r?\\n/'. + --mode mode Type of character to return. Default: 'grapheme'. diff --git a/lib/node_modules/@stdlib/string/truncate-middle/etc/cli_opts.json b/lib/node_modules/@stdlib/string/truncate-middle/etc/cli_opts.json index e51a71a6e33..5a62700e760 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/etc/cli_opts.json +++ b/lib/node_modules/@stdlib/string/truncate-middle/etc/cli_opts.json @@ -2,7 +2,8 @@ "string": [ "len", "seq", - "split" + "split", + "mode" ], "boolean": [ "help", diff --git a/lib/node_modules/@stdlib/string/truncate-middle/lib/main.js b/lib/node_modules/@stdlib/string/truncate-middle/lib/main.js index 49b89a50067..ef0cbb4eaab 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/lib/main.js +++ b/lib/node_modules/@stdlib/string/truncate-middle/lib/main.js @@ -22,11 +22,24 @@ var isString = require( '@stdlib/assert/is-string' ).isPrimitive; var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; -var numGraphemeClusters = require( '@stdlib/string/num-grapheme-clusters' ); -var nextGraphemeClusterBreak = require( '@stdlib/string/next-grapheme-cluster-break' ); +var isPlainObject = require( '@stdlib/assert/is-plain-object' ); +var hasOwnProp = require( '@stdlib/assert/has-own-property' ); +var contains = require( '@stdlib/array/base/assert/contains' ).factory; +var truncateMiddleCodeUnit = require( '@stdlib/string/base/truncate-middle' ); +var truncateMiddleCodePoint = require( '@stdlib/string/base/truncate-middle-code-point' ); +var truncateMiddleGraphemeCluster = require( '@stdlib/string/base/truncate-middle-grapheme-cluster' ); // eslint-disable-line id-length var format = require( '@stdlib/string/format' ); -var round = require( '@stdlib/math/base/special/round' ); -var floor = require( '@stdlib/math/base/special/floor' ); + + +// VARIABLES // + +var MODES = [ 'grapheme', 'code_point', 'code_unit' ]; +var FCNS = { + 'grapheme': truncateMiddleGraphemeCluster, + 'code_point': truncateMiddleCodePoint, + 'code_unit': truncateMiddleCodeUnit +}; +var isMode = contains( MODES ); // MAIN // @@ -37,9 +50,12 @@ var floor = require( '@stdlib/math/base/special/floor' ); * @param {string} str - input string * @param {integer} len - output string length (including sequence) * @param {string} [seq='...'] - custom replacement sequence -* @throws {TypeError} first argument must be a string +* @param {Options} [options] - options +* @param {string} [options.mode="grapheme"] - type of "character" to return (must be either `grapheme`, `code_point`, or `code_unit`) +* @throws {TypeError} must provide a string primitive * @throws {TypeError} second argument must be a nonnegative integer -* @throws {TypeError} third argument must be a string +* @throws {TypeError} options argument must be an object +* @throws {TypeError} must provide valid options * @returns {string} truncated string * * @example @@ -72,54 +88,51 @@ var floor = require( '@stdlib/math/base/special/floor' ); * var out = truncateMiddle( str, 7 ); * // returns '🐺 ... 🐺' */ -function truncateMiddle( str, len, seq ) { - var seqLength; - var fromIndex; - var strLength; - var seqStart; - var nVisual; - var seqEnd; - var idx2; - var idx1; +function truncateMiddle( str, len ) { + var options; + var nargs; + var opts; + var seq; + if ( !isString( str ) ) { throw new TypeError( format( 'invalid argument. First argument must be a string. Value: `%s`.', str ) ); } if ( !isNonNegativeInteger( len ) ) { throw new TypeError( format( 'invalid argument. Second argument must be a nonnegative integer. Value: `%s`.', len ) ); } - if ( arguments.length > 2 ) { + nargs = arguments.length; + opts = { + 'mode': 'grapheme' + }; + if ( nargs === 2 ) { + seq = '...'; + } else if ( nargs === 3 ) { + seq = arguments[ 2 ]; + if ( isPlainObject( seq ) ) { + options = seq; + seq = '...'; + } else if ( !isString( seq ) ) { + throw new TypeError( format( 'invalid argument. Third argument must be a string. Value: `%s`.', seq ) ); + } + } else { // nargs > 3 + seq = arguments[ 2 ]; if ( !isString( seq ) ) { throw new TypeError( format( 'invalid argument. Third argument must be a string. Value: `%s`.', seq ) ); } + options = arguments[ 3 ]; + if ( !isPlainObject( options ) ) { + throw new TypeError( format( 'invalid argument. Options argument must be an object. Value: `%s`.', options ) ); + } } - seq = seq || '...'; - seqLength = numGraphemeClusters( seq ); - strLength = numGraphemeClusters( str ); - fromIndex = 0; - if ( len > strLength ) { - return str; - } - if ( len - seqLength < 0 ) { - return seq.slice( 0, len ); - } - seqStart = round( ( len - seqLength ) / 2 ); - seqEnd = strLength - floor( ( len - seqLength ) / 2 ); - nVisual = 0; - while ( nVisual < seqStart ) { - idx1 = nextGraphemeClusterBreak( str, fromIndex ); - fromIndex = idx1; - nVisual += 1; - } - idx2 = idx1; - while ( idx2 > 0 ) { - idx2 = nextGraphemeClusterBreak( str, fromIndex ); - if ( idx2 >= seqEnd + fromIndex - nVisual ) { - break; + if ( options ) { + if ( hasOwnProp( options, 'mode' ) ) { + opts.mode = options.mode; + if ( !isMode( opts.mode ) ) { + throw new TypeError( format( 'invalid option. `%s` option must be one of the following: "%s". Value: `%s`.', 'mode', MODES.join( '", "' ), opts.mode ) ); + } } - fromIndex = idx2; - nVisual += 1; } - return str.substring( 0, idx1 ) + seq + str.substring( idx2 ); + return FCNS[ opts.mode ](str, len, seq ); } diff --git a/lib/node_modules/@stdlib/string/truncate-middle/test/test.cli.js b/lib/node_modules/@stdlib/string/truncate-middle/test/test.cli.js index 055f17187a5..73e15a3d3e0 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/test/test.cli.js +++ b/lib/node_modules/@stdlib/string/truncate-middle/test/test.cli.js @@ -190,6 +190,66 @@ tape( 'the command-line interface truncates a string in the middle (custom seque } }); +tape( 'the command-line interface supports specifying the type of characters to return', opts, function test( t ) { + var cmd = [ + EXEC_PATH, + '-e', + '"process.stdin.isTTY = true; process.argv[ 2 ] = \'beep boop\'; process.argv[ 3 ] = \'--len=5\'; process.argv[ 4 ] = \'--mode=code_point\'; require( \''+fpath+'\' );"' + ]; + + exec( cmd.join( ' ' ), done ); + + function done( error, stdout, stderr ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( stdout.toString(), 'b...p\n', 'expected value' ); + t.strictEqual( stderr.toString(), '', 'does not print to `stderr`' ); + } + t.end(); + } +}); + +tape( 'the command-line interface supports specifying the type of characters to return (custom sequence)', opts, function test( t ) { + var cmd = [ + EXEC_PATH, + '-e', + '"process.stdin.isTTY = true; process.argv[ 2 ] = \'beep boop\'; process.argv[ 3 ] = \'--len=5\'; process.argv[ 4 ] = \'--seq=!\'; process.argv[ 5 ] = \'--mode=code_point\'; require( \''+fpath+'\' );"' + ]; + + exec( cmd.join( ' ' ), done ); + + function done( error, stdout, stderr ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( stdout.toString(), 'be!op\n', 'expected value' ); + t.strictEqual( stderr.toString(), '', 'does not print to `stderr`' ); + } + t.end(); + } +}); + +tape( 'if provided an invalid option, the command-line interface prints an error and sets a non-zero exit code', opts, function test( t ) { + var cmd = [ + EXEC_PATH, + '-e', + '"process.stdin.isTTY = true; process.argv[ 2 ] = \'beep\'; process.argv[ 3 ] = \'--len=5\'; process.argv[ 4 ] = \'--mode=foo\'; require( \''+fpath+'\' );"' + ]; + + exec( cmd.join( ' ' ), done ); + + function done( error, stdout, stderr ) { + if ( error ) { + t.pass( error.message ); + t.strictEqual( error.code, 1, 'expected exit code' ); + } + t.strictEqual( stdout.toString(), '', 'does not print to `stdout`' ); + t.strictEqual( stderr.toString().length > 0, true, 'expected value' ); + t.end(); + } +}); + tape( 'the command-line interface supports use as a standard stream', opts, function test( t ) { var cmd = [ 'printf "beep boop\nHello World!"', @@ -258,6 +318,29 @@ tape( 'the command-line interface supports specifying a custom delimiter when us } }); +tape( 'the command-line interface supports specifying the type of characters to return when used as a standard stream', opts, function test( t ) { + var cmd = [ + 'printf \'beep boop\tHello World!\'', + '|', + EXEC_PATH, + fpath, + '--len=5', + '--mode code_point' + ]; + + exec( cmd.join( ' ' ), done ); + + function done( error, stdout, stderr ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( stdout.toString(), 'b...!\n', 'expected value' ); + t.strictEqual( stderr.toString(), '', 'does not print to `stderr`' ); + } + t.end(); + } +}); + tape( 'the command-line interface supports specifying a custom delimiter when used as a standard stream (regexp)', opts, function test( t ) { var cmd = [ 'printf \'beep boop\tHello World!\'', @@ -281,6 +364,29 @@ tape( 'the command-line interface supports specifying a custom delimiter when us } }); +tape( 'when used as a standard stream, if provided an invalid option, the command-line interface prints an error and sets a non-zero exit code', opts, function test( t ) { + var cmd = [ + 'printf \'beep boop\tHello World!\'', + '|', + EXEC_PATH, + fpath, + '--len=5', + '--mode=foo' + ]; + + exec( cmd.join( ' ' ), done ); + + function done( error, stdout, stderr ) { + if ( error ) { + t.pass( error.message ); + t.strictEqual( error.code, 1, 'expected exit code' ); + } + t.strictEqual( stdout.toString(), '', 'does not print to `stdout`' ); + t.strictEqual( stderr.toString().length > 0, true, 'expected value' ); + t.end(); + } +}); + tape( 'when used as a standard stream, if an error is encountered when reading from `stdin`, the command-line interface prints an error and sets a non-zero exit code', opts, function test( t ) { var script; var opts; diff --git a/lib/node_modules/@stdlib/string/truncate-middle/test/test.js b/lib/node_modules/@stdlib/string/truncate-middle/test/test.js index e7b87730d10..d40153713b7 100644 --- a/lib/node_modules/@stdlib/string/truncate-middle/test/test.js +++ b/lib/node_modules/@stdlib/string/truncate-middle/test/test.js @@ -88,7 +88,7 @@ tape( 'the function throws an error if not provided a nonnegative integer as its } }); -tape( 'the function throws an error if provided a non-string as a third argument', function test( t ) { +tape( 'the function throws an error if provided an invalid third argument (custom sequence)', function test( t ) { var values; var i; @@ -99,7 +99,6 @@ tape( 'the function throws an error if provided a non-string as a third argument void 0, true, [], - {}, function noop() {} ]; @@ -115,7 +114,117 @@ tape( 'the function throws an error if provided a non-string as a third argument } }); -tape( 'the function truncates a string to the specified length', function test( t ) { +tape( 'the function throws an error if provided an invalid third argument', function test( t ) { + var values; + var i; + + values = [ + 5, + NaN, + null, + void 0, + true, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + truncateMiddle( 'beep boop', 5, value, {} ); + }; + } +}); + +tape( 'the function throws an error if provided a non-object as a fourth argument', function test( t ) { + var values; + var i; + + values = [ + 5, + NaN, + null, + void 0, + true, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + truncateMiddle( 'beep boop', 5, '>>>', value ); + }; + } +}); + +tape( 'the function throws an error if provided a `mode` option which is not a supported mode (third argument)', function test( t ) { + var values; + var i; + + values = [ + 'abc', + 3, + null, + true, + void 0, + NaN, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + truncateMiddle( 'beep boop', 5, { + 'mode': value + }); + }; + } +}); + +tape( 'the function throws an error if provided a `mode` option which is not a supported mode (fourth argument)', function test( t ) { + var values; + var i; + + values = [ + 'abc', + 3, + null, + true, + void 0, + NaN, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws an error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + truncateMiddle( 'beep boop', 5, '>>>', { + 'mode': value + }); + }; + } +}); + +tape( 'the function truncates a string to the specified length (default)', function test( t ) { var expected; var actual; var str; @@ -154,52 +263,169 @@ tape( 'the function truncates a string to the specified length', function test( t.end(); }); -tape( 'the function truncates a string to the specified length (custom replacement sequence)', function test( t ) { +tape( 'the function truncates a string to the specified length (custom replacement sequence; mode=grapheme)', function test( t ) { var expected; var actual; + var opts; var str; var len; + opts = { + 'mode': 'grapheme' + }; + str = 'beep boop'; len = 5; expected = 'be|op'; - actual = truncateMiddle( str, len, '|' ); + actual = truncateMiddle( str, len, '|', opts ); t.strictEqual( actual, expected, 'returns expected value' ); str = 'beep boop'; len = 10; expected = 'beep boop'; - actual = truncateMiddle( str, len, '!' ); + actual = truncateMiddle( str, len, '!', opts ); t.strictEqual( actual, expected, 'returns expected value' ); str = 'beep boop'; len = 3; expected = 'b!p'; - actual = truncateMiddle( str, len, '!' ); + actual = truncateMiddle( str, len, '!', opts ); t.strictEqual( actual, expected, 'returns expected value' ); str = 'beep boop'; len = 5; expected = 'beπŸ˜ƒop'; - actual = truncateMiddle( str, len, 'πŸ˜ƒ' ); + actual = truncateMiddle( str, len, 'πŸ˜ƒ', opts ); t.strictEqual( actual, expected, 'returns expected value' ); str = 'beep boop foo bar'; len = 10; expected = 'be πŸ™ƒ πŸ™ƒ πŸ™ƒar'; - actual = truncateMiddle( str, len, ' πŸ™ƒ πŸ™ƒ πŸ™ƒ' ); + actual = truncateMiddle( str, len, ' πŸ™ƒ πŸ™ƒ πŸ™ƒ', opts ); t.strictEqual( actual, expected, 'returns expected value' ); str = '🐺 Wolf Brothers 🐺'; len = 8; expected = '🐺 Wo>s 🐺'; - actual = truncateMiddle( str, len, '>' ); + actual = truncateMiddle( str, len, '>', opts ); t.strictEqual( actual, expected, 'returns expected value' ); str = 'Hello, world, says πŸ€–, the robot!'; len = 10; expected = 'HelloπŸ€–bot!'; - actual = truncateMiddle( str, len, 'πŸ€–' ); + actual = truncateMiddle( str, len, 'πŸ€–', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function truncates a string to the specified length (custom replacement sequence; mode=code_point)', function test( t ) { + var expected; + var actual; + var opts; + var str; + var len; + + opts = { + 'mode': 'code_point' + }; + + str = 'beep boop'; + len = 5; + expected = 'be|op'; + actual = truncateMiddle( str, len, '|', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop'; + len = 10; + expected = 'beep boop'; + actual = truncateMiddle( str, len, '!', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop'; + len = 3; + expected = 'b!p'; + actual = truncateMiddle( str, len, '!', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop'; + len = 5; + expected = 'beπŸ˜ƒop'; + actual = truncateMiddle( str, len, 'πŸ˜ƒ', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop foo bar'; + len = 10; + expected = 'be πŸ™ƒ πŸ™ƒ πŸ™ƒar'; + actual = truncateMiddle( str, len, ' πŸ™ƒ πŸ™ƒ πŸ™ƒ', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = '🐺 Wolf Brothers 🐺'; + len = 8; + expected = '🐺 W>rs 🐺'; + actual = truncateMiddle( str, len, '>', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'Hello, world, says πŸ€–, the robot!'; + len = 10; + expected = 'HelloπŸ€–bot!'; + actual = truncateMiddle( str, len, 'πŸ€–', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function truncates a string to the specified length (custom replacement sequence; mode=code_unit)', function test( t ) { + var expected; + var actual; + var opts; + var str; + var len; + + opts = { + 'mode': 'code_unit' + }; + + str = 'beep boop'; + len = 5; + expected = 'be|op'; + actual = truncateMiddle( str, len, '|', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop'; + len = 10; + expected = 'beep boop'; + actual = truncateMiddle( str, len, '!', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop'; + len = 3; + expected = 'b!p'; + actual = truncateMiddle( str, len, '!', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop'; + len = 5; + expected = 'beπŸ˜ƒp'; + actual = truncateMiddle( str, len, 'πŸ˜ƒ', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'beep boop foo bar'; + len = 10; + expected = 'b πŸ™ƒ πŸ™ƒ πŸ™ƒ'; + actual = truncateMiddle( str, len, ' πŸ™ƒ πŸ™ƒ πŸ™ƒ', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = '🐺 Wolf Brothers 🐺'; + len = 8; + expected = '🐺 W> 🐺'; + actual = truncateMiddle( str, len, '>', opts ); + t.strictEqual( actual, expected, 'returns expected value' ); + + str = 'Hello, world, says πŸ€–, the robot!'; + len = 10; + expected = 'HellπŸ€–bot!'; + actual = truncateMiddle( str, len, 'πŸ€–', opts ); t.strictEqual( actual, expected, 'returns expected value' ); t.end();