@@ -3,38 +3,104 @@ import {
33} from "./matchers" ;
44import { NormalizerFn } from "./custom-sort-types" ;
55import { CollatorCompare , CollatorTrueAlphabeticalCompare } from "./custom-sort" ;
6+ import {
7+ SNB ,
8+ MDVConverter ,
9+ SpecValueConverter ,
10+ ValueConverters
11+ } from "./value-converters" ;
612
13+ type MDataValueType = string | number | boolean | Array < any >
714export interface MDataMatcher {
8- ( mdataValue : string ) : boolean
15+ ( mdataValue : MDataValueType | undefined ) : boolean
916}
1017
11- export type SN = string | number
12- export type SNResult < T extends SN > = T extends number ? number : string
13- export type CompareFn < T extends SN > = ( a : T , b : T ) => number
14- export interface MDataMatcherFactory < T extends SN > {
15- ( specsMatch : string | RegExpMatchArray , compareFn : CompareFn < SNResult < T > > , mdvConverter : MDVConverter < SNResult < T > > ) : MDataMatcher
18+ export type SorNorB < T = SNB > = T extends number ? number : ( T extends string ? string : boolean )
19+ export type CompareFn < T = SNB > = ( a : T , b : T ) => number
20+
21+ export interface MDataMatcherFactory < T extends SNB > {
22+ ( specsMatch : string | RegExpMatchArray ,
23+ compareFn : CompareFn < SorNorB < T > > ,
24+ //mdvConverter: MDVConverter<SorNorB<T>>,
25+ //typeRepresentative: SorNorB<T>
26+ ) : MDataMatcher | undefined
1627}
1728
18- interface ValueMatcherSpec < T extends SN > {
29+ interface ValueMatcherSpec < T extends SNB > {
1930 specPattern : string | RegExp ,
20- valueMatcherFnFactory : MDataMatcherFactory < SNResult < T > >
21- compareFn : CompareFn < SNResult < T > >
22- mdvConterter ? : MDVConverter < SNResult < T > >
31+ valueMatcherFnFactory : MDataMatcherFactory < SorNorB < T > >
32+ compareFn : CompareFn < SorNorB < T > >
33+ // mdvConterter: MDVConverter<SorNorB <T>>
2334 unitTestsId : string
2435}
2536
37+ // Syntax sugar to enforce TS type checking on matchers configurations
38+ function newStingValueMatcherSpec ( vc : ValueConverters , unitTestId : string , regex : RegExp , trueAlphabetical ?: boolean ) : ValueMatcherSpec < string > {
39+ return {
40+ specPattern : regex ,
41+ valueMatcherFnFactory : getPlainValueMatcherFnFactory < string > ( vc , vc . specToStringConverter . bind ( vc ) , '' /* type representative */ ) ,
42+ compareFn : trueAlphabetical ? CollatorTrueAlphabeticalCompare : CollatorCompare ,
43+ unitTestsId : unitTestId
44+ }
45+ }
46+ function newNumberValueMatcherSpec ( vc : ValueConverters , unitTestId : string , regex : RegExp , representative : number ) : ValueMatcherSpec < number > {
47+ return {
48+ specPattern : regex ,
49+ valueMatcherFnFactory : getPlainValueMatcherFnFactory < number > (
50+ vc ,
51+ ( representative == ~ ~ representative ) ? vc . specToIntConverter . bind ( vc ) : vc . specToFloatConverter . bind ( vc ) ,
52+ representative
53+ ) ,
54+ compareFn : ( representative == ~ ~ representative ) ? CompareIntFn : CompareFloatFn ,
55+ unitTestsId : unitTestId
56+ }
57+ }
58+
59+ function newBooleanValueMatcherSpec ( vc : ValueConverters , unitTestId : string , regex : RegExp ) : ValueMatcherSpec < boolean > {
60+ return {
61+ specPattern : regex ,
62+ valueMatcherFnFactory : getPlainValueMatcherFnFactory < boolean > ( vc , vc . specToBooleanConverter . bind ( vc ) , true /* type representative */ ) ,
63+ compareFn : CompareBoolFn ,
64+ unitTestsId : unitTestId
65+ }
66+ }
67+
2668export interface MDataMatcherParseResult {
2769 m : MDataMatcher
2870 remainder : string
2971}
3072
31- const VALUE_MATCHER_REGEX = / v a l u e \( ( [ ^ ) ] + ) \) / // 001 === 1
32- const VALUE_TRUE_ALPHABETIC_MATCHER_REGEX = / v a l u e E \( ( [ ^ ) ] + ) \) / // 001 != 1
33- function getPlainValueMatcherFn ( specsMatch : RegExpMatchArray , compareFn : CompareFn < string > ) {
34- const EXACT_VALUE_IDX = 1 // Related to the spec regexp
35- const expectedValue = specsMatch [ EXACT_VALUE_IDX ] . trim ( )
36- return ( mdataValue : string ) : boolean => {
37- return compareFn ( mdataValue , expectedValue ) === 0
73+ const VALUE_MATCHER_REGEX = / v a l u e \( ( [ ^ ) ] * ) \) / // 001 === 1
74+ const STR_VALUE_MATCHER_REGEX = / v a l u e S \( ( [ ^ ) ] * ) \) / // 001 === 1
75+ const VALUE_MATCHER_WITH_DEFAULT_REGEX = / v a l u e D \( ( [ ^ : ] * ) : ( [ ^ ) ] + ) \) / // 001 === 1
76+ const VALUE_TRUE_ALPHABETIC_MATCHER_REGEX = / v a l u e E \( ( [ ^ ) ] * ) \) / // 001 != 1
77+ const VALUE_TRUE_ALPHABETIC_MATCHER_WITH_DEFAULT_REGEX = / v a l u e E D \( ( [ ^ : ] * ) : ( [ ^ ) ] * ) \) / // 001 != 1
78+
79+ const INT_VALUE_MATCHER_REGEX = / v a l u e N \( ( \s * ( [ - + ] ? \d + (?: E [ - + ] ? \d + ) ? ) \s * ) \) / i
80+ const FLOAT_VALUE_MATCHER_REGEX = / v a l u e F \( \s * ( [ - + ] ? \d + \. \d + (?: E [ - + ] ? \d + ) ? ) \s * \) / i
81+ const BOOL_VALUE_MATCHER_REGEX = / v a l u e B \( \s * ( t r u e | f a l s e | y e s | n o | \d ) \s * \) / i // for \d only 0 or 1 are accepted, intentionally \d spec here
82+
83+ function getPlainValueMatcherFnFactory < T extends SNB > ( vc : ValueConverters , specValueConverter : SpecValueConverter < SorNorB < T > > , theType : any ) : MDataMatcherFactory < T > {
84+ return ( specsMatch : RegExpMatchArray , compareFn : CompareFn < SorNorB < T > > ) : MDataMatcher | undefined => {
85+ const EXACT_VALUE_IDX = 1 // Related to the spec regexp
86+ const DEFAULT_MDATA_VALUE_FOR_EMPTY_VALUE_IDX = 2 // Related to the spec regexp
87+ const expectedValueString : string | undefined = specsMatch [ EXACT_VALUE_IDX ] // Intentionally not trimming here - string matchers support spaces
88+ const expectedValue : SorNorB < T > | undefined = specValueConverter ( expectedValueString )
89+ if ( expectedValue === undefined ) {
90+ return undefined // syntax error in expected value in spec
91+ }
92+ let mdvConverter : MDVConverter < SorNorB < T > > | undefined = vc . getMdvConverters ( ) [ typeof theType ]
93+ if ( mdvConverter === undefined ) {
94+ return undefined // Error in the code, theType should be one of the supported types
95+ }
96+ return ( mdataValue : MDataValueType | undefined ) : boolean => {
97+ const mdvToUse = mdataValue !== undefined ? mdataValue : specsMatch [ DEFAULT_MDATA_VALUE_FOR_EMPTY_VALUE_IDX ] ?. trim ( )
98+ const mdv = mdvConverter ( mdvToUse )
99+ if ( mdv === undefined ) {
100+ return false // empty metadata value does not match any expected value
101+ }
102+ return compareFn ( mdv , expectedValue ) === 0
103+ }
38104 }
39105}
40106
@@ -51,30 +117,31 @@ const RANGE_NUMERIC_MATCHER_REGEX_FLOAT = /rangeF([[(])\s*?(-?\d+\.\d+)?\s*,\s*(
51117
52118const CompareIntFn : CompareFn < number > = ( a : number , b : number ) => a - b
53119const CompareFloatFn : CompareFn < number > = ( a : number , b : number ) => a - b
54- type MDVConverter < T extends SN > = ( s : string ) => SNResult < T >
120+ const CompareBoolFn : CompareFn < boolean > = ( a : boolean , b : boolean ) => a === b ? 0 : ( a ? 1 : - 1 )
55121
122+ /*
56123enum RangeEdgeType { INCLUSIVE, EXCLUSIVE}
57- function getRangeMatcherFn < T extends SN > ( specsMatch : RegExpMatchArray , compareFn : CompareFn < SNResult < T > > , mdvConverter : MDVConverter < T > ) {
124+ function getRangeMatcherFn<T extends SN>(specsMatch: RegExpMatchArray, compareFn: CompareFn<SorN <T>>, mdvConverter: MDVConverter<SorN<T> >) {
58125 const RANGE_START_TYPE_IDX = 1
59126 const RANGE_START_IDX = 2
60127 const RANGE_END_IDX = 3
61128 const RANGE_END_TYPE_IDX = 4
62129 const rangeStartType: RangeEdgeType = specsMatch[RANGE_START_TYPE_IDX] === '(' ? RangeEdgeType.EXCLUSIVE : RangeEdgeType.INCLUSIVE
63- const rangeStartValue : SNResult < T > = mdvConverter ( specsMatch [ RANGE_START_IDX ] . trim ( ) )
64- const rangeEndValue : SNResult < T > = mdvConverter ( specsMatch [ RANGE_END_IDX ] . trim ( ) )
130+ const rangeStartValue: SorN <T>|undefined = mdvConverter(specsMatch[RANGE_START_IDX]? .trim())
131+ const rangeEndValue: SorN <T>|undefined = mdvConverter(specsMatch[RANGE_END_IDX]? .trim())
65132 const rangeEndType: RangeEdgeType = specsMatch[RANGE_END_TYPE_IDX] === ')' ? RangeEdgeType.EXCLUSIVE : RangeEdgeType.INCLUSIVE
66- return ( mdataValue : string ) : boolean => {
67- let rangeStartMatched = true
68- const mdv : SNResult < T > = mdvConverter ( mdataValue )
69- if ( rangeStartValue ) {
133+ return (mdataValue: string|undefined ): boolean => {
134+ const mdv: SorN<T>|undefined = mdvConverter(mdataValue?.trim())
135+ let rangeStartMatched = mdv!==undefined
136+ if (mdv!==undefined && rangeStartValue!==undefined ) { // rangeStartValue can be '0' or numeric 0
70137 if (rangeStartType === RangeEdgeType.INCLUSIVE) {
71138 rangeStartMatched = compareFn (mdv, rangeStartValue) >= 0
72139 } else {
73140 rangeStartMatched = compareFn (mdv, rangeStartValue) > 0
74141 }
75142 }
76- let rangeEndMatched = true
77- if ( rangeEndValue ) {
143+ let rangeEndMatched = mdv!==undefined
144+ if (mdv!==undefined && rangeEndValue!==undefined ) { // rangeStartValue can be '0' or numeric 0
78145 if (rangeEndType === RangeEdgeType.INCLUSIVE) {
79146 rangeEndMatched = compareFn (mdv, rangeEndValue) <= 0
80147 } else {
@@ -85,73 +152,97 @@ function getRangeMatcherFn<T extends SN>(specsMatch: RegExpMatchArray, compareFn
85152 return rangeStartMatched && rangeEndMatched
86153 }
87154}
155+ */
88156
89- const ValueMatchers : ValueMatcherSpec < SN > [ ] = [
90- { specPattern : VALUE_MATCHER_REGEX ,
91- valueMatcherFnFactory : getPlainValueMatcherFn ,
92- compareFn : CollatorCompare ,
93- unitTestsId : 'value'
94- } , {
95- specPattern : VALUE_TRUE_ALPHABETIC_MATCHER_REGEX ,
96- valueMatcherFnFactory : getPlainValueMatcherFn ,
97- compareFn : CollatorTrueAlphabeticalCompare ,
98- unitTestsId : 'valueE'
99- } , {
100- specPattern : RANGE_MATCHER_REGEX ,
101- valueMatcherFnFactory : getRangeMatcherFn ,
102- compareFn : CollatorCompare ,
103- unitTestsId : 'range'
104- } , {
105- specPattern : RANGE_TRUE_ALPHABETIC_MATCHER_REGEX ,
106- valueMatcherFnFactory : getRangeMatcherFn ,
107- compareFn : CollatorTrueAlphabeticalCompare ,
108- unitTestsId : 'rangeE'
109- } , {
110- specPattern : RANGE_NUMERIC_MATCHER_REGEX_INT ,
111- valueMatcherFnFactory : getRangeMatcherFn ,
112- compareFn : CompareIntFn ,
113- mdvConterter : ( s : string ) => ~ ~ s ,
114- unitTestsId : 'rangeN'
115- } , {
116- specPattern : RANGE_NUMERIC_MATCHER_REGEX_FLOAT ,
117- valueMatcherFnFactory : getRangeMatcherFn ,
118- compareFn : CompareFloatFn ,
119- mdvConterter : ( s : string ) => parseFloat ( s ) ,
120- unitTestsId : 'rangeF'
121- } , {
122- specPattern : 'any-value' , // Artificially added for testing purposes
123- valueMatcherFnFactory : ( ) => ( s : string ) => true ,
124- compareFn : CollatorCompare , // Not used
125- unitTestsId : 'any-value-explicit'
126- }
127- ]
157+ let valueMatchersCache : ValueMatcherSpec < SNB > [ ] | undefined = undefined
158+
159+ const valueConverters = new ValueConverters ( )
160+
161+ // Dependency injection of valueConverters for unit testing purposes
162+ function getValueMatchers ( vc ?: ValueConverters ) {
163+ return valueMatchersCache ??= [
164+ newStingValueMatcherSpec ( vc ?? valueConverters , 'value' , VALUE_MATCHER_REGEX ) ,
165+ newStingValueMatcherSpec ( vc ?? valueConverters , 'valueS' , STR_VALUE_MATCHER_REGEX ) ,
166+ newStingValueMatcherSpec ( vc ?? valueConverters , 'valueD' , VALUE_MATCHER_WITH_DEFAULT_REGEX ) ,
167+ newStingValueMatcherSpec ( vc ?? valueConverters , 'valueE' , VALUE_TRUE_ALPHABETIC_MATCHER_REGEX , true ) ,
168+ newStingValueMatcherSpec ( vc ?? valueConverters , 'valueED' , VALUE_TRUE_ALPHABETIC_MATCHER_WITH_DEFAULT_REGEX , true ) ,
169+ newNumberValueMatcherSpec ( vc ?? valueConverters , 'valueN' , INT_VALUE_MATCHER_REGEX , 1 /* type representative */ ) ,
170+ newNumberValueMatcherSpec ( vc ?? valueConverters , 'valueF' , FLOAT_VALUE_MATCHER_REGEX , 1.1 /* type representative */ ) ,
171+ newBooleanValueMatcherSpec ( vc ?? valueConverters , 'valueB' , BOOL_VALUE_MATCHER_REGEX ) ,
172+ /*
173+
174+ // Range matchers
175+ {
176+ specPattern: RANGE_MATCHER_REGEX,
177+ valueMatcherFnFactory: getRangeMatcherFn,
178+ compareFn: CollatorCompare,
179+ unitTestsId: 'range'
180+ },{
181+ specPattern: RANGE_TRUE_ALPHABETIC_MATCHER_REGEX,
182+ valueMatcherFnFactory: getRangeMatcherFn,
183+ compareFn: CollatorTrueAlphabeticalCompare,
184+ unitTestsId: 'rangeE'
185+ },{
186+ specPattern: RANGE_NUMERIC_MATCHER_REGEX_INT,
187+ valueMatcherFnFactory: getRangeMatcherFn,
188+ compareFn: CompareIntFn,
189+ mdvConterter:
190+ unitTestsId: 'rangeN'
191+ },{
192+ specPattern: RANGE_NUMERIC_MATCHER_REGEX_FLOAT,
193+ valueMatcherFnFactory: getRangeMatcherFn,
194+ compareFn: CompareFloatFn,
195+ mdvConterter:
196+ unitTestsId: 'rangeF'
197+ },*/ {
198+ specPattern : 'any-value' , // Artificially added for testing purposes
199+ valueMatcherFnFactory : ( ) => ( s : any ) => true ,
200+ compareFn : ( a , b ) => 0 , // Not used
201+ unitTestsId : 'any-value-explicit'
202+ }
203+ ]
204+ }
128205
129206export const tryParseAsMDataMatcherSpec = ( s : string ) : MDataMatcherParseResult | undefined => {
130207 // Simplistic initial implementation of the idea, not closing the way to more complex implementations
131- for ( const matcherSpec of ValueMatchers ) {
208+ for ( const matcherSpec of getValueMatchers ( ) ) {
132209 if ( 'string' === typeof matcherSpec . specPattern && s . trim ( ) . startsWith ( matcherSpec . specPattern ) ) {
133- return {
134- m : matcherSpec . valueMatcherFnFactory ( matcherSpec . specPattern , matcherSpec . compareFn , ( s : string ) => s ) ,
210+ const mdMatcher : MDataMatcher | undefined = matcherSpec . valueMatcherFnFactory ( matcherSpec . specPattern , matcherSpec . compareFn )
211+ return mdMatcher ? {
212+ m : mdMatcher ,
135213 remainder : s . substring ( matcherSpec . specPattern . length ) . trim ( )
136- }
214+ } : undefined
137215 } else { // regexp
138216 const match = s . match ( matcherSpec . specPattern )
139217 if ( match ) {
140- return {
141- m : matcherSpec . valueMatcherFnFactory ( match , matcherSpec . compareFn , matcherSpec . mdvConterter ?? ( s => s ) ) ,
218+ const mdMatcher : MDataMatcher | undefined = matcherSpec . valueMatcherFnFactory ( match , matcherSpec . compareFn )
219+ return mdMatcher ? {
220+ m : mdMatcher ,
142221 remainder : s . substring ( match [ 0 ] . length ) . trim ( )
143- }
222+ } : undefined
144223 }
145224 }
146225 }
147226 return undefined
148227}
149228
150229export const _unitTests = {
151- matcherFn_value : ValueMatchers . find ( ( it ) => it . unitTestsId === 'value' ) ,
152- matcherFn_range : ValueMatchers . find ( ( it ) => it . unitTestsId === 'range' ) ,
153- matcherFn_rangeE : ValueMatchers . find ( ( it ) => it . unitTestsId === 'rangeE' ) ,
154- matcherFn_rangeN : ValueMatchers . find ( ( it ) => it . unitTestsId === 'rangeN' ) ,
155- matcherFn_rangeF : ValueMatchers . find ( ( it ) => it . unitTestsId === 'rangeF' ) ,
156- matcherFn_anyValue : ValueMatchers . find ( ( it ) => it . unitTestsId === 'any-value-explicit' ) ,
230+ getMatchers ( vc : ValueConverters ) {
231+ const valueMatchers = getValueMatchers ( vc )
232+ return {
233+ matcherFn_value : valueMatchers . find ( ( it ) => it . unitTestsId === 'value' ) ,
234+ matcherFn_valueS : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueS' ) ,
235+ matcherFn_valueD : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueD' ) ,
236+ matcherFn_valueE : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueE' ) ,
237+ matcherFn_valueED : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueED' ) ,
238+ matcherFn_valueN : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueN' ) ,
239+ matcherFn_valueF : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueF' ) ,
240+ matcherFn_valueB : valueMatchers . find ( ( it ) => it . unitTestsId === 'valueB' ) ,
241+ matcherFn_range : valueMatchers . find ( ( it ) => it . unitTestsId === 'range' ) ,
242+ matcherFn_rangeE : valueMatchers . find ( ( it ) => it . unitTestsId === 'rangeE' ) ,
243+ matcherFn_rangeN : valueMatchers . find ( ( it ) => it . unitTestsId === 'rangeN' ) ,
244+ matcherFn_rangeF : valueMatchers . find ( ( it ) => it . unitTestsId === 'rangeF' ) ,
245+ matcherFn_anyValue : valueMatchers . find ( ( it ) => it . unitTestsId === 'any-value-explicit' ) ,
246+ }
247+ }
157248}
0 commit comments