@gmod/gff
Version:
read and write GFF3 data as streams
213 lines (212 loc) • 7.82 kB
JavaScript
;
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