const DEFAULT_ROOT_ACCOUNTS = [ 'Assets' , 'Liabilities' , 'Equity' , 'Income' , 'Expenses' , ] ; export const COMMODITY_NAME_REGEXP = /^[A-Z]+$/ ; const COMMODITY_REGEXP = /^[\d-]{10} commodity ([A-Z]+)$/umg ; const PAYEE_REGEXP = /^[\d-]{10} (txn|\*) "([^"]+)" ".*/umg ; function getOptionRegexp ( name ) { return new RegExp ( `^option "${ name }"\\s+"(.+)"` , 'um' ) ; } function getCustomOptionRegexp ( name ) { return new RegExp ( `^[\\d-]{10} custom "(?:bcm_option|fava-option)" "${ name }" "(.+)"` , 'um' ) ; } function parseOptionArrayValue ( value ) { const result = JSON . parse ( value . replace ( /'/g , '"' ) ) ; if ( ! Array . isArray ( result ) ) { throw new Error ( 'Invalid option value.' ) ; } return result ; } function uniqueAndSortedByCount ( items ) { const counts = items . reduce ( ( result , item ) => { if ( ! result [ item ] ) { result [ item ] = 1 ; } else { result [ item ] += 1 ; } return result ; } , { } ) ; return Object . keys ( counts ) . sort ( ( v1 , v2 ) => { return - ( counts [ v1 ] - counts [ v2 ] ) ; } ) ; } export class BeancountFileContent { text : string ; constructor ( text ) { this . text = text ; } private getRootAccounts ( ) { return DEFAULT_ROOT_ACCOUNTS . map ( ( name ) => { const regexp = getOptionRegexp ( `name_${ name . toLowerCase ( ) }` ) ; const match = this . text . match ( regexp ) ; return match ? match [ 1 ] : name ; } ) ; } getTitle ( ) { const regexp = getOptionRegexp ( 'title' ) ; const match = this . text . match ( regexp ) ; return match ? match [ 1 ] : 'Untitled' ; } getOperatingCurrency ( ) { const regexp = getOptionRegexp ( 'operating_currency' ) ; const match = this . text . match ( regexp ) ; return match ? match [ 1 ] : '' ; } getTransactionFlags ( ) { const regexp = getCustomOptionRegexp ( 'transaction_flags' ) ; const match = this . text . match ( regexp ) ; if ( match ) { try { return parseOptionArrayValue ( match [ 1 ] ) ; } catch { } } return [ '*' , '!' ] ; } getAccountOrder ( ) { const regexp = getCustomOptionRegexp ( 'account_order' ) ; const match = this . text . match ( regexp ) ; return match ? match [ 1 ] : 'from_to' ; } getIndentation ( ) { const regexp = getCustomOptionRegexp ( 'indent' ) ; const match = this . text . match ( regexp ) ; if ( match ) { try { return parseInt ( match [ 1 ] ) ; } catch { } } return 4 ; } getAccountNameRegexp ( ) { const roots = this . getRootAccounts ( ) . join ( '|' ) ; return new RegExp ( `^(${ roots }):[^\\s]+[^\\s:]$` ) ; } getAccounts ( ) { const roots = this . getRootAccounts ( ) . join ( '|' ) ; const accountRegexp = new RegExp ( `\\s+((${ roots }):[^\\s]+[^\\s:])(\\s|$)` , 'umg' ) ; const matches = this . text [ 'matchAll' ] ( accountRegexp ) ; const accounts = Array . from ( matches , ( match ) => match [ 1 ] ) ; return uniqueAndSortedByCount ( accounts ) ; } getCommodities ( ) { const matches = this . text [ 'matchAll' ] ( COMMODITY_REGEXP ) ; const commodities = Array . from ( matches , ( match ) => match [ 1 ] ) . sort ( ) ; return commodities ; } getPayees ( ) { const matches = this . text [ 'matchAll' ] ( PAYEE_REGEXP ) ; const payees = Array . from ( matches , ( match ) => match [ 2 ] ) ; return uniqueAndSortedByCount ( payees ) ; } append ( text ) { let prefix ; if ( this . text . endsWith ( '\n\n' ) ) { prefix = '' ; } else if ( this . text . endsWith ( '\n' ) ) { prefix = '\n' ; } else { prefix = '\n\n' ; } this . text += `${ prefix }${ text }` ; } }