UNPKG

@gmod/bed

Version:

A BED file format parser with autoSql support

107 lines (98 loc) 3.52 kB
import parser from './autoSql.ts' import types from './defaultTypes.ts' import { detectTypes, AutoSqlSchema, AutoSqlPreSchema } from './util.ts' const strandMap = { '.': 0, '-': -1, '+': 1 } // heuristic that a BED file is BED12 like...the number in col 10 is // blockCount-like function isBed12Like(fields: string[]) { return ( fields.length >= 12 && !Number.isNaN(Number.parseInt(fields[9], 10)) && fields[10]?.split(',').filter(f => !!f).length === Number.parseInt(fields[9], 10) ) } export default class BED { public autoSql: AutoSqlSchema private attemptDefaultBed?: boolean constructor(arguments_: { autoSql?: string; type?: string } = {}) { if (arguments_.autoSql) { this.autoSql = detectTypes( // @ts-expect-error parser.parse(arguments_.autoSql) as AutoSqlPreSchema, ) } else if (arguments_.type) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!types[arguments_.type]) { throw new Error('Type not found') } this.autoSql = detectTypes(types[arguments_.type]) } else { this.autoSql = detectTypes(types.defaultBedSchema) this.attemptDefaultBed = true } } /* * parses a line of text as a BED line with the loaded autoSql schema * * @param line - a BED line as tab delimited text or array * @param opts - supply opts.uniqueId * @return a object representing a feature */ parseLine(line: string | string[], options: { uniqueId?: string } = {}) { const { autoSql } = this const { uniqueId } = options const fields = Array.isArray(line) ? line : line.split('\t') let feature = {} as Record<string, any> if ( !this.attemptDefaultBed || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (this.attemptDefaultBed && isBed12Like(fields)) ) { for (let index = 0; index < autoSql.fields.length; index++) { const autoField = autoSql.fields[index] let columnValue: any = fields[index] const { isNumeric, isArray, arrayIsNumeric, name } = autoField if (columnValue === null || columnValue === undefined) { break } if (columnValue !== '.') { if (isNumeric) { const number_ = Number(columnValue) columnValue = Number.isNaN(number_) ? columnValue : number_ } else if (isArray) { columnValue = columnValue.split(',') if (columnValue.at(-1) === '') { columnValue.pop() } if (arrayIsNumeric) { columnValue = columnValue.map(Number) } } feature[name] = columnValue } } } else { const fieldNames = ['chrom', 'chromStart', 'chromEnd', 'name'] feature = Object.fromEntries( fields.map((f, index) => [fieldNames[index] || 'field' + index, f]), ) feature.chromStart = +feature.chromStart feature.chromEnd = +feature.chromEnd if (!Number.isNaN(Number.parseFloat(feature.field4))) { feature.score = +feature.field4 delete feature.field4 } if (feature.field5 === '+' || feature.field5 === '-') { feature.strand = feature.field5 delete feature.field5 } } if (uniqueId) { feature.uniqueId = uniqueId } feature.strand = strandMap[feature.strand as keyof typeof strandMap] || 0 feature.chrom = decodeURIComponent(feature.chrom) return feature } }