From 5607bf19279494f8be1ce81d5ec089fe6dcf1023 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Thu, 1 Dec 2016 08:44:20 -0500 Subject: [PATCH] Replace Currency formatter with StringFormat This is a much more general solution. * src/validate/formatter/Currency.js: Remove trait. * test/validate/formatter/CurrencyTest.js: Remove test case. * src/validate/formatter/StringFormat.js: Add trait. * test/validate/formatter/StringFormatTest.js: Add test case. --- src/validate/formatter/Currency.js | 75 --------- src/validate/formatter/StringFormat.js | 178 ++++++++++++++++++++ test/validate/formatter/CurrencyTest.js | 60 ------- test/validate/formatter/StringFormatTest.js | 115 +++++++++++++ 4 files changed, 293 insertions(+), 135 deletions(-) delete mode 100644 src/validate/formatter/Currency.js create mode 100644 src/validate/formatter/StringFormat.js delete mode 100644 test/validate/formatter/CurrencyTest.js create mode 100644 test/validate/formatter/StringFormatTest.js diff --git a/src/validate/formatter/Currency.js b/src/validate/formatter/Currency.js deleted file mode 100644 index 5ecf339..0000000 --- a/src/validate/formatter/Currency.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Currency formatter - * - * Copyright (C) 2016 LoVullo Associates, Inc. - * - * This file is part of liza. - * - * liza is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -var Trait = require( 'easejs' ).Trait, - ValidatorFormatter = require( '../ValidatorFormatter' ); - - -/** - * Formats amount as currency - * - * This does not guarantee that the value is a number; this should be - * mixed in atop of a formatter that does guarantee such. - */ -module.exports = Trait( 'Currency' ) - .implement( ValidatorFormatter ) - .extend( -{ - /** - * Parse item as currency - * - * @param {string} data data to parse - * - * @return {string} data formatted for storage - */ - 'virtual abstract override public parse': function( data ) - { - return this.__super( data ).replace( /^\$*/g, '' ); - }, - - - /** - * Format amount as a currency - * - * @param {string} data data to format for display - * - * @return {string} data formatted for display - */ - 'virtual abstract override public retrieve': function( data ) - { - return this.styleCurrency( this.__super( data ) ); - }, - - - /** - * Style amount with currency symbol - * - * @param {string} amount amount to style - * - * @return {string} formatted number - */ - 'virtual protected styleCurrency': function( amount ) - { - return ( ''+amount === '' ) - ? amount - : '$' + amount; - }, -} ); diff --git a/src/validate/formatter/StringFormat.js b/src/validate/formatter/StringFormat.js new file mode 100644 index 0000000..c82510a --- /dev/null +++ b/src/validate/formatter/StringFormat.js @@ -0,0 +1,178 @@ +/** + * @license + * StringFormat formatter + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @module validate/formatter + */ + +var Trait = require( 'easejs' ).Trait, + ValidatorFormatter = require( '../ValidatorFormatter' ); + + +/** + * Basic data formatting + * + * `StringFormat` effectively allows for the definition of prefixes + * and suffixes by using a format string of the form `'pre %s post'`. + * + * For example, the format string `'$%sUSD'` applied to the + * input `'$25.00USD'` would yield `'25.00'` after parsing and + * re-yield the original string when retrieved. + * + * If `StringFormat` is mixed in with other formatters that + * modify the input, then the former will be applied after earlier + * formatters process the data. For example: + * + * @example + * let fmt = EchoFormatter + * .use( Number( 2 ) ) + * .use( StringFormatter( '$%sUSD' ) )(); + * + * fmt.parse( '$25USD' ); // => 25.00 + * fmt.retrieve( '25' ); // => $25.00USD + * + * The simple, restricted format string in place of a regular expression + * allows for a declarative stacking of formatters without concern as + * to whether the format was written correctly. Regular expressions, + * however, might well need associated tests for confidence in the + * implementation, which complicates the design. Simply: it's easy to + * reason about. + */ +module.exports = Trait( 'StringFormat' ) + .implement( ValidatorFormatter ) + .extend( +{ + /** + * Prefix string + * + * @type {string} + */ + 'private _pre': '', + + /** + * Postfix string + * + * @type {string} + */ + 'private _post': '', + + + /** + * Define format string + * + * The format string must have a single `'%s'` denoting the + * placement of the data. + * + * @param {string} format format string with single `'%s'` + */ + __mixin: function( format ) + { + var parts = this.parseFormat( ''+format ); + + this._pre = parts.pre; + this._post = parts.post; + }, + + + /** + * Extract prefix and suffix from format string FORMAT + * + * Everything before the `'%s'` is the prefix, and everything + * after is the suffix. + * + * @param {string} format format string with single `'%s'` + * + * @return {Object.} prefix and suffix of FORMAT + * + * @throws {Error} if FORMAT does not have exactly one `'%s'` + */ + 'virtual protected parseFormat': function( format ) + { + var parts = format.split( '%s' ); + + if ( parts.length !== 2 ) + { + throw Error( + "Format string must have a single '%s': " + format + ); + } + + return { + pre: parts[ 0 ], + post: parts[ 1 ] + }; + }, + + + /** + * Remove prefix and suffix from data + * + * @param {string} data data to parse + * + * @return {string} data formatted for storage + */ + 'virtual abstract override public parse': function( data ) + { + return this.__super( this._stripPrePost( data ) ); + }, + + + /** + * Recursively strip prefixes and suffixes from STR + * + * This simply allows us to avoid having to use regexes and, + * consequently, worry about escaping format strings. + * + * @param {string} str string to strip + * + * @return {string} stripped string + */ + 'private _stripPrePost': function( str ) + { + if ( this._pre && ( str.substr( 0, this._pre.length ) === this._pre ) ) + { + return this._stripPrePost( + str.substr( this._pre.length ) + ); + } + + if ( this._post && ( str.substr( -this._post.length ) === this._post ) ) + { + return this._stripPrePost( + str.substr( 0, str.length - this._post.length ) + ); + } + + return str; + }, + + + /** + * Format data by adding prefix and suffix + * + * @param {string} data data to format for display + * + * @return {string} data formatted for display + */ + 'virtual abstract override public retrieve': function( data ) + { + return this._pre + this.__super( data ) + this._post; + }, +} ); diff --git a/test/validate/formatter/CurrencyTest.js b/test/validate/formatter/CurrencyTest.js deleted file mode 100644 index 968e2e3..0000000 --- a/test/validate/formatter/CurrencyTest.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Currency formatter test - * - * Copyright (C) 2016 LoVullo Associates, Inc. - * - * This file is part of liza. - * - * liza is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -var liza = require( '../../../' ), - Sut = liza.validate.formatter.Currency, - EchoFormatter = liza.validate.formatter.EchoFormatter, - common = require( './common' ); - - -describe( 'validate.formatter.Currency', function() -{ - common.testValidate( EchoFormatter.use( Sut )(), { - // should format anything given to it, with or without prefix - "1": [ "1", "$1" ], - "foo": [ "foo", "$foo" ], - "+": [ "+", "$+" ], - "$foo": [ "foo", "$foo" ], - - // empty shouldn't format as anything - "": [ "", "" ], - "$": [ "", "" ], - "$$": [ "", "" ], - - // make sure these aren't considered to be empty - "0": [ "0", "$0" ], - "$0": [ "0", "$0" ], - - // be lax on input - "$$foo": [ "foo", "$foo" ], - "$$$$$$$12.34": [ "12.34", "$12.34" ], - } ); - - - common.testMixin( - EchoFormatter, - Sut, - 'foo', - '123', - 'foo123', - '$foo123' - ); -} ); diff --git a/test/validate/formatter/StringFormatTest.js b/test/validate/formatter/StringFormatTest.js new file mode 100644 index 0000000..3580d92 --- /dev/null +++ b/test/validate/formatter/StringFormatTest.js @@ -0,0 +1,115 @@ +/** + * @license + * StringFormat formatter test + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var liza = require( '../../../' ), + expect = require( 'chai' ).expect, + Sut = liza.validate.formatter.StringFormat, + EchoFormatter = liza.validate.formatter.EchoFormatter, + common = require( './common' ); + + +describe( 'validate.formatter.StringFormat', function() +{ + common.testValidate( EchoFormatter.use( Sut( 'PRE%sPOST' ) )(), { + // basic prefix/suffix + "": [ "", "PREPOST" ], + "foo": [ "foo", "PREfooPOST" ], + "PREfoo": [ "foo", "PREfooPOST" ], + "barPOST": [ "bar", "PREbarPOST" ], + "PREbazPOST": [ "baz", "PREbazPOST" ], + + // only prefix/suffix + "PRE": [ "", "PREPOST" ], + "POST": [ "", "PREPOST" ], + "PREPOST": [ "", "PREPOST" ], + + // repeated prefix/suffix normalization + "PREPREfoo": [ "foo", "PREfooPOST" ], + "barPOSTPOST": [ "bar", "PREbarPOST" ], + "PREPREbazPOSTPOSTPOST": [ "baz", "PREbazPOST" ], + "PREPREPOSTPOST": [ "", "PREPOST" ], + + // convoluted interpretations + "PREfooPOSTPRE": [ "fooPOSTPRE", "PREfooPOSTPREPOST" ], + "PREmooPREfooPOST": [ "mooPREfoo", "PREmooPREfooPOST" ], + "mooPREfoo": [ "mooPREfoo", "PREmooPREfooPOST" ], + } ); + + + // only prefix format + common.testValidate( EchoFormatter.use( Sut( 'BEG%s' ) )(), { + "foo": [ "foo", "BEGfoo" ], + "BEGfoo": [ "foo", "BEGfoo" ], + } ); + + + // only suffix format + common.testValidate( EchoFormatter.use( Sut( '%sEND' ) )(), { + "fooEND": [ "foo", "fooEND" ], + "fooEND": [ "foo", "fooEND" ], + } ); + + + // no prefix or suffix + common.testValidate( EchoFormatter.use( Sut( '%s' ) )(), { + "foo": [ "foo", "foo" ], + } ); + + + describe( 'given multiple %s', function() + { + it( 'throws an error', function() + { + expect( function() + { + EchoFormatter.use( Sut( 'foo%sbar%sbaz' ) )(); + } ).to.throw( Error ); + } ); + } ); + + + describe( 'given no %s', function() + { + it( 'throws an error', function() + { + expect( function() + { + EchoFormatter.use( Sut( '' ) )(); + } ).to.throw( Error ); + + expect( function() + { + EchoFormatter.use( Sut( 'Foo' ) )(); + } ).to.throw( Error ); + } ); + } ); + + + common.testMixin( + EchoFormatter, + Sut( 'PRE%sPOST' ), + 'base', + 'PREfooPOST', + 'basefoo', + 'PREbasePREfooPOSTPOST' + ); +} );