import getValue from 'get-value' ; import setValue from 'set-value' ; import { objectHash } from '@jbrowse/core/util' ; import { isSource , isTrack } from './util' ; import { Config , Track , Source , Store , Names } from './types' ; export function parseJB1Json ( config , url ) { if ( typeof config === 'string' ) { let parsedConf ; try { parsedConf = JSON . parse ( config ) ; } catch ( error ) { throw new Error ( `${ error } when parsing configuration.` ) ; } return regularizeConf ( parsedConf , url ) ; } return regularizeConf ( config , url ) ; } export function parseJB1Conf ( config , url ) { let parsedConf ; try { parsedConf = parse ( config , url ) ; } catch ( error ) { throw new Error ( `${ error } when parsing configuration.` ) ; } return regularizeConf ( parsedConf , url ) ; } function isAlwaysArray ( varName ) { if ( varName === 'include' ) { return true ; } return false ; } function parse ( text , url ) { let section = [ ] ; let keyPath ; let operation ; let value ; const data = { tracks : { } } ; let lineNumber ; function recordVal ( ) { if ( value !== undefined ) { let parsedValue ; try { const match = value . match ( /^json:(.+)/i ) ; if ( match ) { parsedValue = JSON . parse ( match [ 1 ] ) ; } else if ( /^[+-]?[\d.,]+([eE][-+]?\d+)?$/ . test ( value ) ) { parsedValue = parseFloat ( value . replace ( /,/g , '' ) ) ; } else { parsedValue = value ; } if ( ! keyPath ) { throw new Error ( `Error parsing in section ${ section . join ( ' - ' ) }` ) ; } const path = section . concat ( keyPath ) . join ( '.' ) ; if ( operation === '+=' ) { let existing = getValue ( data , path ) ; if ( existing ) { if ( ! Array . isArray ( existing ) ) { existing = [ existing ] ; } } else { existing = [ ] ; } existing . push ( parsedValue ) ; parsedValue = existing ; } if ( parsedValue === 'true' ) { parsedValue = true ; } if ( parsedValue === 'false' ) { parsedValue = false ; } setValue ( data , path , parsedValue ) ; } catch ( e ) { throw new Error ( `syntax error${ url ? ` in ${ url }` : '' }${ lineNumber ? ` at line ${ lineNumber - 1 }` : '' }` ) ; } } } text . split ( '\n' ) . forEach ( ( textLine , i ) : void => { lineNumber = i + 1 ; const line = textLine . replace ( /^\s*#.+/ , '' ) ; let match ; if ( ( match = line . match ( /^\s*\[([^\]]+)/ ) ) ) { recordVal ( ) ; keyPath = undefined ; value = undefined ; section = match [ 1 ] . trim ( ) . split ( /\s*\.\s*/ ) ; if ( section . length === 1 && section [ 0 ] . toLowerCase ( ) === 'general' ) { section = [ ] ; } } else if ( ( match = line . match ( value === undefined ? /^([^+=]+)(\+?=)(.*)/ : /^(\S[^+=]+)(\+?=)(.*)/ ) ) ) { recordVal ( ) ; keyPath = match [ 1 ] . trim ( ) . split ( /\s*\.\s*/ ) ; [ , , operation ] = match ; if ( isAlwaysArray ( section . concat ( keyPath ) . join ( '.' ) ) ) { operation = '+=' ; } value = match [ 3 ] . trim ( ) ; } else if ( keyPath !== undefined && ( match = line . match ( /^\s{0,4}\+\s*(.+)/ ) ) ) { recordVal ( ) ; operation = '+=' ; value = match [ 1 ] . trim ( ) ; } else if ( value !== undefined && ( match = line . match ( /^\s+(\S.*)/ ) ) ) { value += value . length ? ` ${ match [ 1 ] . trim ( ) }` : match [ 1 ] . trim ( ) ; } else { recordVal ( ) ; keyPath = undefined ; value = undefined ; } } ) ; recordVal ( ) ; return data ; } param conf param url returns export function regularizeConf ( conf , url ) { if ( conf . tracks && ! Array . isArray ( conf . tracks ) ) { if ( isTrack ( conf . tracks ) ) { conf . tracks = [ conf . tracks ] ; } else { const tracks = [ ] ; for ( const label of Object . keys ( conf . tracks ) ) { const track = conf . tracks [ label ] ; if ( isTrack ( track ) ) { tracks . push ( track ) ; } else { tracks . push ( { label , ... track } ) ; } } conf . tracks = tracks ; } } const meta = conf . trackMetadata ; if ( meta && meta . sources ) { if ( typeof meta . sources === 'string' ) { meta . sources = [ meta . sources ] ; } if ( isSource ( meta . sources ) ) { meta . sources = [ meta . sources ] ; } if ( ! Array . isArray ( meta . sources ) ) { const sources = [ ] ; for ( const name of Object . keys ( meta . sources ) ) { const source = meta . sources [ name ] ; if ( ! ( 'name' in source ) ) { source . name = name ; } sources . push ( source ) ; } meta . sources = sources ; } meta . sources = meta . sources . map ( ( sourceDef ) : Source => { if ( typeof sourceDef === 'string' ) { const newSourceDef = { url : sourceDef } ; const typeMatch = sourceDef . match ( /\.(\w+)$/ ) ; if ( typeMatch ) { newSourceDef . type = typeMatch [ 1 ] . toLowerCase ( ) ; } return newSourceDef ; } return sourceDef ; } ) ; } conf . sourceUrl = conf . sourceUrl || url ; if ( conf . sourceUrl . startsWith ( '/' ) ) { conf . sourceUrl = new URL ( conf . sourceUrl , window . location . href ) . href ; } conf . baseUrl = conf . baseUrl || new URL ( '.' , conf . sourceUrl ) . href ; if ( conf . baseUrl . length && ! conf . baseUrl . endsWith ( '/' ) ) { conf . baseUrl += '/' ; } if ( conf . sourceUrl ) { const addBase = [ ] ; if ( conf . tracks ) { addBase . push ( ... conf . tracks ) ; } if ( conf . stores ) { addBase . push ( ... Object . values ( conf . stores ) ) ; } if ( conf . names ) { addBase . push ( conf . names ) ; } addBase . forEach ( ( t ) : void => { if ( ! t . baseUrl ) { t . baseUrl = conf . baseUrl || '/' ; } } ) ; if ( conf . refSeqs && typeof conf . refSeqs === 'string' ) { conf . refSeqs = new URL ( conf . refSeqs , conf . sourceUrl ) . href ; } if ( conf . nameUrl ) { conf . nameUrl = new URL ( conf . nameUrl , conf . sourceUrl ) . href ; } } conf . stores = conf . stores || { } ; ( conf . tracks || [ ] ) . forEach ( ( trackConfig ) : void => { if ( trackConfig . config ) { const c = trackConfig . config ; delete trackConfig . config ; trackConfig = { ... c , ... trackConfig } ; } if ( trackConfig . store ) { return ; } let trackClassName ; if ( trackConfig . type === 'FeatureTrack' ) { trackClassName = 'JBrowse/View/Track/HTMLFeatures' ; } else if ( trackConfig . type === 'ImageTrack' ) { trackClassName = 'JBrowse/View/Track/FixedImage' ; } else if ( trackConfig . type === 'ImageTrack.Wiggle' ) { trackClassName = 'JBrowse/View/Track/FixedImage/Wiggle' ; } else if ( trackConfig . type === 'SequenceTrack' ) { trackClassName = 'JBrowse/View/Track/Sequence' ; } else { trackClassName = regularizeClass ( 'JBrowse/View/Track' , trackConfig . type ) ; } trackConfig . type = trackClassName ; synthesizeTrackStoreConfig ( conf , trackConfig ) ; if ( trackConfig . histograms ) { if ( ! trackConfig . histograms . baseUrl ) { trackConfig . histograms . baseUrl = trackConfig . baseUrl ; } synthesizeTrackStoreConfig ( conf , trackConfig . histograms ) ; } } ) ; return conf ; } param root param className function regularizeClass ( root , className ) { if ( ! className ) { return '' ; } if ( ! className . includes ( '/' ) ) { className = `${ root }/${ className }` ; } className = className . replace ( /^\// , '' ) ; return className ; } function guessStoreClass ( trackConfig , urlTemplate ) { if ( ! trackConfig ) { return '' ; } if ( trackConfig . type && trackConfig . type . includes ( '/FixedImage' ) ) { return `JBrowse/Store/TiledImage/Fixed${ trackConfig . backendVersion === 0 ? '_v0' : '' }` ; } if ( /\.jsonz?$/i . test ( urlTemplate ) ) { return `JBrowse/Store/SeqFeature/NCList${ trackConfig . backendVersion === 0 ? '_v0' : '' }` ; } if ( /\.bam$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/BAM' ; } if ( /\.cram$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/CRAM' ; } if ( /\.gff3?$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/GFF3' ; } if ( /\.bed$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/BED' ; } if ( /\.vcf.b?gz$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/VCFTabix' ; } if ( /\.gff3?.b?gz$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/GFF3Tabix' ; } if ( /\.bed.b?gz$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/BEDTabix' ; } if ( /\.(bw|bigwig)$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/BigWig' ; } if ( /\.(bb|bigbed)$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/BigBed' ; } if ( /\.(fa|fasta)$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/IndexedFasta' ; } if ( /\.(fa|fasta)\.b?gz$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/BgzipIndexedFasta' ; } if ( /\.2bit$/i . test ( urlTemplate ) ) { return 'JBrowse/Store/SeqFeature/TwoBit' ; } if ( trackConfig . type && trackConfig . type . endsWith ( '/Sequence' ) ) { return 'JBrowse/Store/Sequence/StaticChunked' ; } return '' ; } function synthesizeTrackStoreConfig ( mainConf , trackConfig ) { const { urlTemplate = '' } = trackConfig ; let storeClass ; if ( trackConfig . storeClass ) { storeClass = regularizeClass ( 'JBrowse/Store' , trackConfig . storeClass ) ; } else { storeClass = guessStoreClass ( trackConfig , urlTemplate ) ; } if ( ! storeClass ) { console . warn ( `Unable to determine an appropriate data store to use with track '${ trackConfig . label }', please explicitly specify a storeClass in the configuration.` ) ; return ; } const storeConf = { ... trackConfig , type : storeClass } ; if ( ( storeClass === 'JBrowse/Store/Sequence/StaticChunked' || storeClass === 'JBrowse/Store/Sequence/IndexedFasta' || storeClass === 'JBrowse/Store/SeqFeature/IndexedFasta' || storeClass === 'JBrowse/Store/SeqFeature/BgzipIndexedFasta' || storeClass === 'JBrowse/Store/SeqFeature/TwoBit' || storeClass === 'JBrowse/Store/Sequence/TwoBit' || trackConfig . useAsRefSeqStore ) && ! ( mainConf . stores && mainConf . stores . refseqs ) ) { storeConf . name = 'refseqs' ; } else { storeConf . name = `store${ objectHash ( storeConf ) }` ; } if ( ! mainConf . stores ) { mainConf . stores = { } ; } mainConf . stores [ storeConf . name ] = storeConf ; trackConfig . store = storeConf . name ; }