UNPKG

@gmod/gff

Version:

read and write GFF3 data as streams

213 lines (212 loc) 7.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatFile = exports.formatStream = exports.formatSync = exports.parseStringSync = exports.parseStream = void 0; const stream_1 = require("stream"); const string_decoder_1 = require("string_decoder"); const parse_1 = __importDefault(require("./parse")); const util_1 = require("./util"); // call a callback on the next process tick if running in // an environment that supports it function _callback(callback) { if (process && process.nextTick) process.nextTick(callback); else callback(); } // shared arg processing for the parse routines function _processParseOptions(options) { const out = Object.assign({ encoding: 'utf8', parseFeatures: true, parseDirectives: false, parseSequences: true, parseComments: false, bufferSize: 1000, disableDerivesFromReferences: false }, options); if (options.parseAll) { out.parseFeatures = true; out.parseDirectives = true; out.parseComments = true; out.parseSequences = true; } return out; } class GFFTransform extends stream_1.Transform { constructor(inputOptions = {}) { super({ objectMode: true }); this.textBuffer = ''; const options = _processParseOptions(inputOptions); this.encoding = inputOptions.encoding || 'utf8'; this.decoder = new string_decoder_1.StringDecoder(); const push = this.push.bind(this); this.parser = new parse_1.default({ featureCallback: options.parseFeatures ? push : undefined, directiveCallback: options.parseDirectives ? push : undefined, commentCallback: options.parseComments ? push : undefined, sequenceCallback: options.parseSequences ? push : undefined, errorCallback: (err) => this.emit('error', err), bufferSize: options.bufferSize, disableDerivesFromReferences: options.disableDerivesFromReferences, }); } _addLine(data) { if (data) { this.parser.addLine(data); } } _nextText(buffer) { const pieces = (this.textBuffer + buffer).split(/\r?\n/); this.textBuffer = pieces.pop() || ''; pieces.forEach((piece) => this._addLine(piece)); } _transform(chunk, _encoding, callback) { this._nextText(this.decoder.write(chunk)); _callback(callback); } _flush(callback) { if (this.decoder.end) this._nextText(this.decoder.end()); if (this.textBuffer != null) this._addLine(this.textBuffer); this.parser.finish(); _callback(callback); } } /** * Parse a stream of text data into a stream of feature, directive, comment, * an sequence objects. * * @param options - Parsing options * @returns stream (in objectMode) of parsed items */ function parseStream(options = {}) { return new GFFTransform(options); } exports.parseStream = parseStream; function parseStringSync(str, inputOptions = {}) { if (!str) return []; const options = _processParseOptions(inputOptions); const items = []; const push = items.push.bind(items); const parser = new parse_1.default({ featureCallback: options.parseFeatures ? push : undefined, directiveCallback: options.parseDirectives ? push : undefined, commentCallback: options.parseComments ? push : undefined, sequenceCallback: options.parseSequences ? push : undefined, disableDerivesFromReferences: options.disableDerivesFromReferences || false, bufferSize: Infinity, errorCallback: (err) => { throw err; }, }); str.split(/\r?\n/).forEach(parser.addLine.bind(parser)); parser.finish(); return items; } exports.parseStringSync = parseStringSync; /** * Format an array of GFF3 items (features,directives,comments) into string of * GFF3. Does not insert synchronization (###) marks. * * @param items - Array of features, directives, comments and/or sequences * @returns the formatted GFF3 */ function formatSync(items) { // sort items into seq and other const other = []; const sequences = []; items.forEach((i) => { if ('sequence' in i) sequences.push(i); else other.push(i); }); let str = other.map(util_1.formatItem).join(''); if (sequences.length) { str += '##FASTA\n'; str += sequences.map(util_1.formatSequence).join(''); } return str; } exports.formatSync = formatSync; class FormattingTransform extends stream_1.Transform { constructor(options = {}) { super(Object.assign(options, { objectMode: true })); this.linesSinceLastSyncMark = 0; this.haveWeEmittedData = false; this.fastaMode = false; this.minLinesBetweenSyncMarks = options.minSyncLines || 100; this.insertVersionDirective = options.insertVersionDirective || false; } _transform(chunk, _encoding, callback) { // if we have not emitted anything yet, and this first // chunk is not a gff-version directive, emit one let str; if (!this.haveWeEmittedData && this.insertVersionDirective) { const thisChunk = Array.isArray(chunk) ? chunk[0] : chunk; if ('directive' in thisChunk) { if (thisChunk.directive !== 'gff-version') { this.push('##gff-version 3\n'); } } } // if it's a sequence chunk coming down, emit a FASTA directive and // change to FASTA mode if ('sequence' in chunk && !this.fastaMode) { this.push('##FASTA\n'); this.fastaMode = true; } if (Array.isArray(chunk)) str = chunk.map(util_1.formatItem).join(''); else str = (0, util_1.formatItem)(chunk); this.push(str); if (this.linesSinceLastSyncMark >= this.minLinesBetweenSyncMarks) { this.push('###\n'); this.linesSinceLastSyncMark = 0; } else { // count the number of newlines in this chunk let count = 0; for (let i = 0; i < str.length; i += 1) { if (str[i] === '\n') count += 1; } this.linesSinceLastSyncMark += count; } this.haveWeEmittedData = true; _callback(callback); } } /** * Format a stream of features, directives, comments and/or sequences into a * stream of GFF3 text. * * Inserts synchronization (###) marks automatically. * * @param options - parser options */ function formatStream(options = {}) { return new FormattingTransform(options); } exports.formatStream = formatStream; /** * Format a stream of features, directives, comments and/or sequences into a * GFF3 file and write it to the filesystem. * Inserts synchronization (###) marks and a ##gff-version * directive automatically (if one is not already present). * * @param stream - the stream to write to the file * @param filename - the file path to write to * @param options - parser options * @returns promise for null that resolves when the stream has been written */ function formatFile(stream, writeStream, options = {}) { const newOptions = Object.assign({ insertVersionDirective: true }, options); return new Promise((resolve, reject) => { stream .pipe(new FormattingTransform(newOptions)) .on('end', () => resolve(null)) .on('error', reject) .pipe(writeStream); }); } exports.formatFile = formatFile; //# sourceMappingURL=api.js.map