import { Functions as fn } from './Functions' ; export type Formatter < T > = ( item : T ) => string ; export interface FormatterMap < T > { [ key ] : Formatter < T > ; } export interface FormatSection < T > { size : number ; formats : FormatterMap < T > ; } export interface FormatEscapes { [ first ] : { start : string ; startEscape : string ; end : string ; endEscape : string ; } ; } export class Format < T > { private cached : FormatterMap < T > = { } ; private sections : FormatSection < T > [ ] = [ ] ; private escapes : FormatEscapes ; public constructor ( formats , escapes = { } ) { this . add ( formats ) ; this . escapes = escapes ; } public add ( map ) ; public add ( key , formatter ) ; public add ( keyOrMap , formatter ? ) { if ( fn . isString ( keyOrMap ) ) { this . getSection ( keyOrMap . length ) . formats [ keyOrMap ] = formatter ; } else { for ( const key in keyOrMap ) { this . getSection ( key . length ) . formats [ key ] = keyOrMap [ key ] ; } } return this ; } public getSection ( size ) { const guess = this . sections . length - size ; const guessSection = this . sections [ guess ] ; if ( guessSection && guessSection . size === size ) { return guessSection ; } for ( const section of this . sections ) { if ( section . size === size ) { return section ; } } const newSection = { size , formats : { } } ; this . sections . push ( newSection ) ; this . sortBySize ( ) ; return newSection ; } private sortBySize ( ) { this . sections . sort ( ( a , b ) => b . size - a . size ) ; } private getEscaped ( x , i ) { const c = x . charAt ( i ) ; const escaped = this . escapes [ c ] ; if ( ! escaped ) { return false ; } const { start , end , startEscape , endEscape } = escaped ; const possibleStart = x . substring ( i , i + start . length ) ; if ( possibleStart !== start ) { return false ; } const possibleEscapeIndex = i - startEscape . indexOf ( start ) ; const possibleEscape = x . substring ( possibleEscapeIndex , possibleEscapeIndex + startEscape . length ) ; if ( possibleEscape === startEscape ) { return false ; } i += start . length ; const endOffset = endEscape . indexOf ( end ) ; let last = x . indexOf ( end , i ) ; let content = x . substring ( i , last ) ; i = last - endOffset ; while ( last !== - 1 && x . substring ( i , i + endEscape . length ) === endEscape ) { content = content . substring ( 0 , content . length - endOffset ) ; content += end ; i += endEscape . length ; last = x . indexOf ( end , i ) ; content += x . substring ( i , last ) ; i = last - endOffset ; } if ( last === - 1 ) { return false ; } return [ content , last + end . length ] ; } public getFormatter ( format , cache = false ) { if ( format in this . cached ) { return this . cached [ format ] ; } const sections = this . sections ; const formats = [ ] ; let constant = '' ; for ( let i = 0 ; i < format . length ; i ++ ) { const escaped = this . getEscaped ( format , i ) ; if ( escaped !== false ) { const [ content , end ] = escaped ; constant += content ; i = end - 1 ; continue ; } let handled = false ; for ( let k = 0 ; k < sections . length && ! handled ; k ++ ) { const section = sections [ k ] ; const part = format . substring ( i , i + section . size ) ; if ( part . length === section . size ) { const formatter = section . formats [ part ] ; if ( formatter ) { formats . push ( formatter ) ; i += section . size - 1 ; handled = true ; } } } if ( handled ) { if ( constant ) { const copy = constant ; formats . splice ( formats . length - 1 , 0 , ( ) => copy ) ; constant = '' ; } } else { constant += format . charAt ( i ) ; } } if ( constant ) { formats . push ( ( ) => constant ) ; } const finalFormatter = ( item ) => { let formatted = '' ; for ( const formatter of formats ) { formatted += formatter ( item ) ; } return formatted ; } ; if ( cache ) { this . cached [ format ] = finalFormatter ; } return finalFormatter ; } public format ( format , item , cache = false ) { return this . getFormatter ( format , cache ) ( item ) ; } }